diff --git a/README.md b/README.md index 8ca92cf..809f01d 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,10 @@ Fonts genlock Logs anamorphic desqueeze +solid color layer refactor, cleanup of source files display URL (Maybe clicakable) for control in the windows app (Not on the output) Sound shader as seperate .slang in shader package? runtime date time +Add a value control to the color wheels +![alt text](image.png) \ No newline at end of file diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index cd8f1fa..cfa3a2c 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,31 @@ const char* kDecodeFragmentShaderSource = " fragColor = rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n" "}\n"; +class GdiplusSession +{ +public: + GdiplusSession() + { + Gdiplus::GdiplusStartupInput startupInput; + mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok; + } + + ~GdiplusSession() + { + if (mStarted) + Gdiplus::GdiplusShutdown(mToken); + } + + GdiplusSession(const GdiplusSession&) = delete; + GdiplusSession& operator=(const GdiplusSession&) = delete; + + bool started() const { return mStarted; } + +private: + ULONG_PTR mToken = 0; + bool mStarted = false; +}; + void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage) { if (!errorMessage || errorMessageSize <= 0) @@ -191,11 +217,87 @@ std::vector BuildLocalSdf(const std::vector& alpha return sdf; } +std::vector FlipTextTextureForShaderUv(const std::vector& pixels, unsigned width, unsigned height) +{ + std::vector flipped(pixels.size(), 0); + const std::size_t stride = static_cast(width) * 4; + for (unsigned y = 0; y < height; ++y) + { + const std::size_t srcOffset = static_cast(y) * stride; + const std::size_t dstOffset = static_cast(height - 1 - y) * stride; + std::memcpy(flipped.data() + dstOffset, pixels.data() + srcOffset, stride); + } + return flipped; +} + +void WriteTextMaskDebugDump(const std::string& text, const std::vector& alpha, const std::vector& sdf, unsigned width, unsigned height) +{ + try + { + std::filesystem::path debugDir = std::filesystem::current_path() / "runtime"; + std::filesystem::create_directories(debugDir); + + auto writePgm = [width, height](const std::filesystem::path& path, const std::vector& gray, std::size_t stride) + { + std::ofstream out(path, std::ios::binary); + if (!out) + return; + out << "P5\n" << width << " " << height << "\n255\n"; + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + out.put(static_cast(gray[(static_cast(y) * width + x) * stride])); + } + }; + + writePgm(debugDir / "text-mask-alpha-debug.pgm", alpha, 1); + writePgm(debugDir / "text-mask-sdf-debug.pgm", sdf, 4); + + unsigned alphaMin = 255; + unsigned alphaMax = 0; + unsigned sdfMin = 255; + unsigned sdfMax = 0; + std::size_t alphaLit = 0; + std::size_t sdfLit = 0; + for (unsigned char value : alpha) + { + alphaMin = std::min(alphaMin, value); + alphaMax = std::max(alphaMax, value); + if (value > 0) + ++alphaLit; + } + for (std::size_t index = 0; index < sdf.size(); index += 4) + { + const unsigned char value = sdf[index]; + sdfMin = std::min(sdfMin, value); + sdfMax = std::max(sdfMax, value); + if (value > 127) + ++sdfLit; + } + + std::ostringstream message; + message << "Text mask debug for '" << text << "': alpha min/max/lit=" << alphaMin << "/" << alphaMax << "/" << alphaLit + << ", sdf min/max/gt127=" << sdfMin << "/" << sdfMax << "/" << sdfLit << "\n"; + OutputDebugStringA(message.str().c_str()); + } + catch (...) + { + OutputDebugStringA("Failed to write text mask debug dump.\n"); + } +} + +GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) +{ + GLint location = glGetUniformLocation(program, samplerName.c_str()); + if (location >= 0) + return location; + return glGetUniformLocation(program, (samplerName + "_0").c_str()); +} + bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector& sdf, std::string& error) { - ULONG_PTR gdiplusToken = 0; - Gdiplus::GdiplusStartupInput startupInput; - if (Gdiplus::GdiplusStartup(&gdiplusToken, &startupInput, NULL) != Gdiplus::Ok) + GdiplusSession gdiplus; + if (!gdiplus.started()) { error = "Could not start GDI+ for text rendering."; return false; @@ -210,7 +312,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font { if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok) { - Gdiplus::GdiplusShutdown(gdiplusToken); error = "Could not load packaged font file for text rendering: " + fontPath.string(); return false; } @@ -218,7 +319,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font const INT familyCount = fontCollection.GetFamilyCount(); if (familyCount <= 0) { - Gdiplus::GdiplusShutdown(gdiplusToken); error = "Packaged font did not contain a usable font family: " + fontPath.string(); return false; } @@ -227,7 +327,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font INT found = 0; if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0) { - Gdiplus::GdiplusShutdown(gdiplusToken); error = "Could not read the packaged font family: " + fontPath.string(); return false; } @@ -236,7 +335,9 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB); Gdiplus::Graphics graphics(&bitmap); - graphics.Clear(Gdiplus::Color(0, 0, 0, 0)); + graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); + graphics.Clear(Gdiplus::Color(255, 0, 0, 0)); + graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver); graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); Gdiplus::Font font(fontFamily, 72.0f, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); @@ -256,11 +357,17 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font { Gdiplus::Color pixel; bitmap.GetPixel(x, y, &pixel); - alpha[static_cast(y) * kTextTextureWidth + x] = pixel.GetAlpha(); + BYTE luminance = pixel.GetRed(); + if (pixel.GetGreen() > luminance) + luminance = pixel.GetGreen(); + if (pixel.GetBlue() > luminance) + luminance = pixel.GetBlue(); + alpha[static_cast(y) * kTextTextureWidth + x] = static_cast(luminance); } } sdf = BuildLocalSdf(alpha, kTextTextureWidth, kTextTextureHeight); - Gdiplus::GdiplusShutdown(gdiplusToken); + sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight); + WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight); return true; } @@ -1670,7 +1777,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint); const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; - const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap; + const GLuint shaderTextureBase = state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase; glUseProgram(newProgram.get()); const GLint videoInputLocation = glGetUniformLocation(newProgram.get(), "gVideoInput"); if (videoInputLocation >= 0) @@ -1689,14 +1796,14 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, } for (std::size_t index = 0; index < textureBindings.size(); ++index) { - const GLint textureSamplerLocation = glGetUniformLocation(newProgram.get(), textureBindings[index].samplerName.c_str()); + const GLint textureSamplerLocation = FindSamplerUniformLocation(newProgram.get(), textureBindings[index].samplerName); if (textureSamplerLocation >= 0) glUniform1i(textureSamplerLocation, static_cast(shaderTextureBase + static_cast(index))); } const GLuint textTextureBase = shaderTextureBase + static_cast(textureBindings.size()); for (std::size_t index = 0; index < textBindings.size(); ++index) { - const GLint textSamplerLocation = glGetUniformLocation(newProgram.get(), textBindings[index].samplerName.c_str()); + const GLint textSamplerLocation = FindSamplerUniformLocation(newProgram.get(), textBindings[index].samplerName); if (textSamplerLocation >= 0) glUniform1i(textSamplerLocation, static_cast(textTextureBase + static_cast(index))); } @@ -1704,6 +1811,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, layerProgram.layerId = state.layerId; layerProgram.shaderId = state.shaderId; + layerProgram.shaderTextureBase = shaderTextureBase; layerProgram.program = newProgram.release(); layerProgram.vertexShader = newVertexShader.release(); layerProgram.fragmentShader = newFragmentShader.release(); @@ -1972,9 +2080,18 @@ bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state, if (!RasterizeTextSdf(text, fontPath, sdf, error)) return false; + GLint previousActiveTexture = 0; + GLint previousUnpackBuffer = 0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture); + glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer); + glActiveTexture(GL_TEXTURE0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, textBinding.texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data()); glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast(previousUnpackBuffer)); + glActiveTexture(static_cast(previousActiveTexture)); + textBinding.renderedText = text; textBinding.renderedWidth = kTextTextureWidth; textBinding.renderedHeight = kTextTextureHeight; @@ -1983,8 +2100,7 @@ bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state, void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram) { - const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; - const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap; + const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase; for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index) { glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast(index)); @@ -2023,7 +2139,7 @@ void OpenGLComposite::destroyDecodeShaderProgram() bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector& layerStates, std::string& error) const { const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; - unsigned maxAssetTextures = 0; + unsigned requiredUnits = kSourceHistoryTextureUnitBase; for (const RuntimeRenderState& state : layerStates) { unsigned textTextureCount = 0; @@ -2033,12 +2149,12 @@ bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector(state.textureAssets.size()) + textTextureCount; - if (totalShaderTextures > maxAssetTextures) - maxAssetTextures = totalShaderTextures; + const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures; + if (layerRequiredUnits > requiredUnits) + requiredUnits = layerRequiredUnits; } GLint maxTextureUnits = 0; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits); - const unsigned requiredUnits = kSourceHistoryTextureUnitBase + historyCap + historyCap + maxAssetTextures; const unsigned availableUnits = maxTextureUnits > 0 ? static_cast(maxTextureUnits) : 0u; if (requiredUnits > availableUnits) { @@ -2327,7 +2443,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index); glBindTexture(GL_TEXTURE_2D, 0); } - const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap; + const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase; for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index) { glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast(index)); diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h index 2936f62..3be8815 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h @@ -180,6 +180,7 @@ private: std::string layerId; std::string shaderId; + GLuint shaderTextureBase = 0; GLuint program = 0; GLuint vertexShader = 0; GLuint fragmentShader = 0; diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp index 8272b21..fdcc916 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp @@ -1571,8 +1571,62 @@ void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, co { for (const ShaderParameterDefinition& definition : shaderPackage.parameters) { - if (layerState.parameterValues.find(definition.id) == layerState.parameterValues.end()) + auto valueIt = layerState.parameterValues.find(definition.id); + if (valueIt == layerState.parameterValues.end()) + { layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition); + continue; + } + + JsonValue valueJson; + bool shouldNormalize = true; + switch (definition.type) + { + case ShaderParameterType::Float: + if (valueIt->second.numberValues.empty()) + shouldNormalize = false; + else + valueJson = JsonValue(valueIt->second.numberValues.front()); + break; + case ShaderParameterType::Vec2: + case ShaderParameterType::Color: + valueJson = JsonValue::MakeArray(); + for (double number : valueIt->second.numberValues) + valueJson.pushBack(JsonValue(number)); + break; + case ShaderParameterType::Boolean: + valueJson = JsonValue(valueIt->second.booleanValue); + break; + case ShaderParameterType::Enum: + valueJson = JsonValue(valueIt->second.enumValue); + break; + case ShaderParameterType::Text: + { + const std::string textValue = !valueIt->second.textValue.empty() + ? valueIt->second.textValue + : valueIt->second.enumValue; + if (textValue.empty()) + { + valueIt->second = DefaultValueForDefinition(definition); + shouldNormalize = false; + } + else + { + valueJson = JsonValue(textValue); + } + break; + } + } + + if (!shouldNormalize) + continue; + + ShaderParameterValue normalizedValue; + std::string normalizeError; + if (NormalizeAndValidateValue(definition, valueJson, normalizedValue, normalizeError)) + valueIt->second = normalizedValue; + else + valueIt->second = DefaultValueForDefinition(definition); } } diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp index a015ccc..36cf4ac 100644 --- a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp @@ -100,6 +100,8 @@ std::string BuildTextHelpers(const std::vector& param source << "float sample" << suffix << "(float2 uv)\n" << "{\n" + << "\tif (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)\n" + << "\t\treturn 0.0;\n" << "\treturn " << definition.id << "Texture.Sample(uv).r;\n" << "}\n\n" << "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n" @@ -166,13 +168,14 @@ bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, return false; wrapperSource = ReplaceAll(wrapperSource, "{{PARAMETER_UNIFORMS}}", BuildParameterUniforms(shaderPackage.parameters)); - wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", mMaxTemporalHistoryFrames)); - wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", mMaxTemporalHistoryFrames)); + const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0; + wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount)); + wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount)); wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets)); wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters)); wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters)); - wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", mMaxTemporalHistoryFrames)); - wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", mMaxTemporalHistoryFrames)); + wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount)); + wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount)); wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string()); wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", shaderPackage.entryPoint + "(context)"); return true; diff --git a/image.png b/image.png new file mode 100644 index 0000000..f8ceba4 Binary files /dev/null and b/image.png differ diff --git a/shaders/text-overlay/shader.slang b/shaders/text-overlay/shader.slang index abfdebb..118d47e 100644 --- a/shaders/text-overlay/shader.slang +++ b/shaders/text-overlay/shader.slang @@ -15,11 +15,16 @@ float4 shadeVideo(ShaderContext context) float2 resolution = max(context.outputResolution, float2(1.0, 1.0)); float aspect = resolution.x / resolution.y; float2 textSize = float2(0.72 * scale, 0.09 * scale * aspect); - float2 textUv = (context.uv - position) / max(textSize, float2(0.0001, 0.0001)); + float2 safeTextSize = max(textSize, float2(0.0001, 0.0001)); + float2 textUv = (context.uv - position) / safeTextSize; + bool insideTextRect = textUv.x >= 0.0 && textUv.x <= 1.0 && textUv.y >= 0.0 && textUv.y <= 1.0; - float mask = sampleTitleText(textUv); + float mask = insideTextRect ? sampleTitleText(textUv) : 0.0; float fill = smoothstep(0.48, 0.54, mask); float outline = smoothstep(0.48 - outlineWidth, 0.54 - outlineWidth, mask); + float textAlpha = max(fill * fillColor.a, outline * outlineColor.a); + if (textAlpha <= 0.0001) + return context.sourceColor; float4 base = context.sourceColor; float4 outlineLayer = float4(outlineColor.rgb * outlineColor.a, outline * outlineColor.a); diff --git a/ui/src/components/ParameterField.jsx b/ui/src/components/ParameterField.jsx index 946815a..348941a 100644 --- a/ui/src/components/ParameterField.jsx +++ b/ui/src/components/ParameterField.jsx @@ -280,6 +280,7 @@ export function ParameterField({ layer, parameter, onParameterChange }) { sendValue(event.target.value)} diff --git a/ui/src/hooks/useThrottledParameterValue.js b/ui/src/hooks/useThrottledParameterValue.js index c8e8121..583ecb5 100644 --- a/ui/src/hooks/useThrottledParameterValue.js +++ b/ui/src/hooks/useThrottledParameterValue.js @@ -5,33 +5,34 @@ function valuesMatch(left, right) { } export function useThrottledParameterValue(parameter, onParameterChange) { - const [draftValue, setDraftValue] = useState(parameter.value); - const [appliedValue, setAppliedValue] = useState(parameter.value); + const currentValue = parameter.value === undefined ? parameter.defaultValue : parameter.value; + const [draftValue, setDraftValue] = useState(currentValue); + const [appliedValue, setAppliedValue] = useState(currentValue); const pendingTimeoutRef = useRef(null); - const latestDraftRef = useRef(parameter.value); + const latestDraftRef = useRef(currentValue); const lastSentAtRef = useRef(0); const isInteractingRef = useRef(false); const isDirtyRef = useRef(false); useEffect(() => { - setDraftValue(parameter.value); - setAppliedValue(parameter.value); - latestDraftRef.current = parameter.value; + setDraftValue(currentValue); + setAppliedValue(currentValue); + latestDraftRef.current = currentValue; lastSentAtRef.current = 0; isInteractingRef.current = false; isDirtyRef.current = false; }, [parameter.id]); useEffect(() => { - setAppliedValue(parameter.value); + setAppliedValue(currentValue); latestDraftRef.current = draftValue; - if (isDirtyRef.current && valuesMatch(parameter.value, latestDraftRef.current)) { + if (isDirtyRef.current && valuesMatch(currentValue, latestDraftRef.current)) { isDirtyRef.current = false; } if (!isInteractingRef.current && !isDirtyRef.current) { - setDraftValue(parameter.value); + setDraftValue(currentValue); } - }, [draftValue, parameter.value]); + }, [draftValue, currentValue]); useEffect(() => { return () => {