diff --git a/src/render/RenderThread.cpp b/src/render/RenderThread.cpp index cd34786..6cbbebd 100644 --- a/src/render/RenderThread.cpp +++ b/src/render/RenderThread.cpp @@ -344,7 +344,8 @@ void RenderThread::TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRender std::string commitError; if (TryTakePendingRenderLayers(layers)) { - if (!runtimeRenderScene.CommitRenderLayers(layers, commitError)) + bool structuralChange = false; + if (!runtimeRenderScene.CommitRenderLayers(layers, commitError, &structuralChange)) { RenderCadenceCompositor::TryLog( RenderCadenceCompositor::LogLevel::Error, @@ -354,11 +355,14 @@ void RenderThread::TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRender return; } - RenderCadenceCompositor::TryLog( - RenderCadenceCompositor::LogLevel::Log, - "render-thread", - "Runtime render layer snapshot committed."); - mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed); + if (structuralChange) + { + RenderCadenceCompositor::TryLog( + RenderCadenceCompositor::LogLevel::Log, + "render-thread", + "Runtime render layer snapshot committed."); + mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed); + } return; } diff --git a/src/render/runtime/RuntimeRenderScene.cpp b/src/render/runtime/RuntimeRenderScene.cpp index 302504f..c8c2070 100644 --- a/src/render/runtime/RuntimeRenderScene.cpp +++ b/src/render/runtime/RuntimeRenderScene.cpp @@ -20,9 +20,10 @@ bool RuntimeRenderScene::StartPrepareWorker(std::unique_ptr shar return mPrepareWorker.Start(std::move(sharedWindow), error); } -bool RuntimeRenderScene::CommitRenderLayers(const std::vector& layers, std::string& error) +bool RuntimeRenderScene::CommitRenderLayers(const std::vector& layers, std::string& error, bool* structuralChange) { ConsumePreparedPrograms(); + bool changedStructure = false; std::vector nextOrder; std::vector layersToPrepare; @@ -49,6 +50,7 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vectorshaderId = layer.shaderId; program->pendingFingerprint = fingerprint; layersToPrepare.push_back(layer); + changedStructure = true; } + if (mLayerOrder != nextOrder) + changedStructure = true; mLayerOrder = std::move(nextOrder); if (!layersToPrepare.empty()) mPrepareWorker.Submit(layersToPrepare); + if (structuralChange) + *structuralChange = changedStructure; error.clear(); return true; } diff --git a/src/render/runtime/RuntimeRenderScene.h b/src/render/runtime/RuntimeRenderScene.h index 65bb5da..32f33ce 100644 --- a/src/render/runtime/RuntimeRenderScene.h +++ b/src/render/runtime/RuntimeRenderScene.h @@ -20,7 +20,7 @@ public: ~RuntimeRenderScene(); bool StartPrepareWorker(std::unique_ptr sharedWindow, std::string& error); - bool CommitRenderLayers(const std::vector& layers, std::string& error); + bool CommitRenderLayers(const std::vector& layers, std::string& error, bool* structuralChange = nullptr); bool HasLayers(); void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture = 0); void ShutdownGl(); diff --git a/src/render/runtime/RuntimeShaderRenderer.cpp b/src/render/runtime/RuntimeShaderRenderer.cpp index 1b4ae4f..ccb16cc 100644 --- a/src/render/runtime/RuntimeShaderRenderer.cpp +++ b/src/render/runtime/RuntimeShaderRenderer.cpp @@ -61,6 +61,7 @@ bool RuntimeShaderRenderer::CommitPreparedProgram(RuntimePreparedShaderProgram& DestroyProgram(); return false; } + mTextTextures.RefreshTextTextures(); return true; } @@ -70,6 +71,7 @@ void RuntimeShaderRenderer::UpdateArtifactState(const RuntimeShaderArtifact& art mArtifact.parameterValues = artifact.parameterValues; mArtifact.message = artifact.message; mTextTextures.UpdateArtifactState(artifact); + mTextTextures.RefreshTextTextures(); } bool RuntimeShaderRenderer::BuildPreparedProgram( diff --git a/src/render/runtime/RuntimeTextTextureCache.cpp b/src/render/runtime/RuntimeTextTextureCache.cpp index 19a70f2..8557e09 100644 --- a/src/render/runtime/RuntimeTextTextureCache.cpp +++ b/src/render/runtime/RuntimeTextTextureCache.cpp @@ -88,6 +88,7 @@ bool RuntimeTextTextureCache::Configure(const RuntimeShaderArtifact& artifact, s TextTexture texture; texture.parameterId = definition.id; texture.fontId = definition.fontId; + texture.maxLength = definition.maxLength == 0 ? 64 : definition.maxLength; mTextTextures.push_back(std::move(texture)); } @@ -101,14 +102,21 @@ void RuntimeTextTextureCache::UpdateArtifactState(const RuntimeShaderArtifact& a mArtifact.parameterValues = artifact.parameterValues; } +void RuntimeTextTextureCache::RefreshTextTextures() +{ + for (TextTexture& textTexture : mTextTextures) + { + EnsureTextTexture(textTexture); + } +} + void RuntimeTextTextureCache::BindTextTextures(GLuint program) { for (std::size_t index = 0; index < mTextTextures.size(); ++index) { - TextTexture& textTexture = mTextTextures[index]; - if (!EnsureTextTexture(textTexture)) + const TextTexture& textTexture = mTextTextures[index]; + if (textTexture.texture == 0) continue; - glActiveTexture(GL_TEXTURE0 + kFirstTextTextureUnit + static_cast(index)); glBindTexture(GL_TEXTURE_2D, textTexture.texture); } @@ -318,21 +326,32 @@ bool RuntimeTextTextureCache::EnsureTextTexture(TextTexture& texture) unsigned width = 0; unsigned height = 0; - std::vector pixels = ComposeTextMask(*atlas, text, width, height); + std::vector pixels = ComposeTextMask(*atlas, texture, text, width, height); if (pixels.empty() || width == 0 || height == 0) return false; if (texture.texture == 0) glGenTextures(1, &texture.texture); + GLint previousUnpackAlignment = 4; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousUnpackAlignment); glBindTexture(GL_TEXTURE_2D, texture.texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, static_cast(width), static_cast(height), 0, GL_RED, GL_UNSIGNED_BYTE, pixels.data()); + if (texture.width != width || texture.height != height) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, static_cast(width), static_cast(height), 0, GL_RED, GL_UNSIGNED_BYTE, pixels.data()); + } + else + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(width), static_cast(height), GL_RED, GL_UNSIGNED_BYTE, pixels.data()); + } glBindTexture(GL_TEXTURE_2D, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, previousUnpackAlignment); + glActiveTexture(GL_TEXTURE0); texture.cachedText = text; texture.width = width; @@ -340,7 +359,7 @@ bool RuntimeTextTextureCache::EnsureTextTexture(TextTexture& texture) return texture.texture != 0; } -std::vector RuntimeTextTextureCache::ComposeTextMask(const Atlas& atlas, const std::string& text, unsigned& width, unsigned& height) const +std::vector RuntimeTextTextureCache::ComposeTextMask(const Atlas& atlas, const TextTexture& texture, const std::string& text, unsigned& width, unsigned& height) const { double advance = 0.0; for (unsigned char character : text) @@ -350,7 +369,8 @@ std::vector RuntimeTextTextureCache::ComposeTextMask(const Atlas& advance += glyphIt->second.advance; } - width = (std::max)(1u, static_cast(std::ceil(advance * kFontPixelsPerEm)) + kTextTexturePadding * 2u); + const unsigned fixedWidth = static_cast(std::ceil(static_cast(texture.maxLength) * kFontPixelsPerEm * 0.9)) + kTextTexturePadding * 2u; + width = (std::max)(fixedWidth, static_cast(std::ceil(advance * kFontPixelsPerEm)) + kTextTexturePadding * 2u); height = kTextTextureHeight; std::vector mask(static_cast(width) * height, 0); diff --git a/src/render/runtime/RuntimeTextTextureCache.h b/src/render/runtime/RuntimeTextTextureCache.h index 4f047e8..446f5fd 100644 --- a/src/render/runtime/RuntimeTextTextureCache.h +++ b/src/render/runtime/RuntimeTextTextureCache.h @@ -17,6 +17,7 @@ public: bool Configure(const RuntimeShaderArtifact& artifact, std::string& error); void UpdateArtifactState(const RuntimeShaderArtifact& artifact); + void RefreshTextTextures(); void BindTextTextures(GLuint program); void ShutdownGl(); @@ -57,13 +58,14 @@ private: GLuint texture = 0; unsigned width = 0; unsigned height = 0; + unsigned maxLength = 64; }; bool LoadAtlas(const RenderCadenceCompositor::FontAtlasBuildOutput& output, Atlas& atlas, std::string& error) const; bool LoadAtlasJson(const RenderCadenceCompositor::FontAtlasBuildOutput& output, Atlas& atlas, std::string& error) const; bool LoadAtlasImage(const RenderCadenceCompositor::FontAtlasBuildOutput& output, Atlas& atlas, std::string& error) const; bool EnsureTextTexture(TextTexture& texture); - std::vector ComposeTextMask(const Atlas& atlas, const std::string& text, unsigned& width, unsigned& height) const; + std::vector ComposeTextMask(const Atlas& atlas, const TextTexture& texture, const std::string& text, unsigned& width, unsigned& height) const; const Atlas* FindAtlas(const std::string& fontId) const; static const ShaderParameterValue* FindParameterValue(const RuntimeShaderArtifact& artifact, const std::string& parameterId); static std::string DefaultTextValue(const RuntimeShaderArtifact& artifact, const std::string& parameterId); diff --git a/ui/src/components/ParameterField.jsx b/ui/src/components/ParameterField.jsx index 8dfb6c2..6702f75 100644 --- a/ui/src/components/ParameterField.jsx +++ b/ui/src/components/ParameterField.jsx @@ -359,7 +359,7 @@ export function ParameterField({ layer, parameter, onParameterChange }) { placeholder={parameter.defaultValue ? `Default: ${parameter.defaultValue}` : ""} value={draftValue ?? ""} onFocus={beginInteraction} - onChange={(event) => sendValue(event.target.value)} + onChange={(event) => scheduleSendValue(event.target.value, false, 350)} onBlur={endInteraction} /> diff --git a/ui/src/hooks/useThrottledParameterValue.js b/ui/src/hooks/useThrottledParameterValue.js index f9b4650..48f0893 100644 --- a/ui/src/hooks/useThrottledParameterValue.js +++ b/ui/src/hooks/useThrottledParameterValue.js @@ -82,7 +82,7 @@ export function useThrottledParameterValue(parameter, onParameterChange) { } }; - const scheduleSendValue = (value, immediate = false) => { + const scheduleSendValue = (value, immediate = false, throttleMs = 90) => { setDraftValue(value); latestDraftRef.current = value; isDirtyRef.current = true; @@ -93,7 +93,6 @@ export function useThrottledParameterValue(parameter, onParameterChange) { } const now = Date.now(); - const throttleMs = 90; const elapsed = now - lastSentAtRef.current; if (immediate || elapsed >= throttleMs) {