From 3e8b472f744af46b4bbcf39969b5f663a566d04b Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 5 May 2026 23:18:50 +1000 Subject: [PATCH 1/4] Initial font work --- CMakeLists.txt | 1 + README.md | 9 +- SHADER_CONTRACT.md | 60 +++++ .../OpenGLComposite.cpp | 252 +++++++++++++++++- .../OpenGLComposite.h | 15 +- .../RuntimeHost.cpp | 118 +++++++- .../RuntimeParameterUtils.cpp | 26 ++ .../ShaderCompiler.cpp | 53 ++++ .../ShaderPackageRegistry.cpp | 84 ++++++ .../ShaderTypes.h | 16 +- runtime/templates/shader_wrapper.slang.in | 2 + shaders/studio-color/shader.json | 50 ---- shaders/studio-color/shader.slang | 23 -- shaders/text-overlay/fonts/OFL.txt | 93 +++++++ shaders/text-overlay/fonts/Roboto-Regular.ttf | Bin 0 -> 157208 bytes shaders/text-overlay/shader.json | 62 +++++ shaders/text-overlay/shader.slang | 28 ++ tests/RuntimeParameterUtilsTests.cpp | 21 ++ tests/ShaderPackageRegistryTests.cpp | 27 +- ui/src/components/ParameterField.jsx | 17 ++ 20 files changed, 873 insertions(+), 84 deletions(-) delete mode 100644 shaders/studio-color/shader.json delete mode 100644 shaders/studio-color/shader.slang create mode 100644 shaders/text-overlay/fonts/OFL.txt create mode 100644 shaders/text-overlay/fonts/Roboto-Regular.ttf create mode 100644 shaders/text-overlay/shader.json create mode 100644 shaders/text-overlay/shader.slang diff --git a/CMakeLists.txt b/CMakeLists.txt index 8228721..b1bc11c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ target_link_libraries(LoopThroughWithOpenGLCompositing PRIVATE Ws2_32 Crypt32 Advapi32 + Gdiplus ) target_compile_definitions(LoopThroughWithOpenGLCompositing PRIVATE diff --git a/README.md b/README.md index 8c1dab5..8ca92cf 100644 --- a/README.md +++ b/README.md @@ -203,9 +203,10 @@ Each shader package lives under: shaders// shader.json shader.slang + optional-font-or-texture-assets ``` -See `SHADER_CONTRACT.md` for the manifest schema, parameter types, texture assets, temporal history support, and the Slang entry point contract. +See `SHADER_CONTRACT.md` for the manifest schema, parameter types, texture assets, font/text assets, temporal history support, and the Slang entry point contract. `shaders/text-overlay/` is the reference live text package and bundles Roboto Regular with its OFL license. ## Generated Files @@ -234,4 +235,8 @@ Audio Fonts genlock Logs -anamorphic desqueeze \ No newline at end of file +anamorphic desqueeze +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 diff --git a/SHADER_CONTRACT.md b/SHADER_CONTRACT.md index fa1c460..58b2305 100644 --- a/SHADER_CONTRACT.md +++ b/SHADER_CONTRACT.md @@ -73,6 +73,7 @@ Optional fields: - `category`: UI grouping label. - `entryPoint`: Slang function to call. Defaults to `shadeVideo`. - `textures`: texture assets to load and expose as samplers. +- `fonts`: packaged font assets for live text parameters. - `temporal`: history-buffer requirements. Shader-visible identifiers must be valid Slang-style identifiers: @@ -80,6 +81,7 @@ Shader-visible identifiers must be valid Slang-style identifiers: - `entryPoint` - parameter `id` - texture `id` +- font `id` Use letters, numbers, and underscores only, and start with a letter or underscore. For example, `logoTexture` is valid; `logo-texture` is not valid as a shader-visible texture ID. @@ -180,6 +182,7 @@ Supported types: | `color` | `float4` | `[r, g, b, a]` | | `bool` | `bool` | `true` or `false` | | `enum` | `int` | selected option index | +| `text` | generated texture/helper | string | Float example: @@ -278,12 +281,42 @@ else if (mode == 2) } ``` +Text example: + +```json +{ + "fonts": [ + { "id": "inter", "path": "fonts/Inter-Regular.ttf" } + ], + "parameters": [ + { + "id": "titleText", + "label": "Title", + "type": "text", + "default": "LIVE", + "font": "inter", + "maxLength": 64 + } + ] +} +``` + +Text parameters are runtime-owned strings. They are not emitted as uniform values. Instead, the runtime renders the current string into a single-line SDF mask texture and the shader wrapper exposes helpers based on the parameter id: + +```slang +float mask = sampleTitleText(textUv); +float4 premultipliedText = drawTitleText(textUv, float4(1.0, 1.0, 1.0, 1.0)); +``` + +Text is currently limited to printable ASCII. `maxLength` defaults to `64` and is clamped to `1..256`. The optional `font` field references a packaged font declared in `fonts`; if no font is specified, the runtime uses its fallback sans-serif renderer. + Parameter validation: - Float values are clamped to `min`/`max` if provided. - `vec2` must have exactly 2 numbers. - `color` must have exactly 4 numbers. - Enum defaults must match one of the declared option values. +- Text defaults must be strings. Non-printable characters are dropped and values are clamped to `maxLength`. - Non-finite numeric values are rejected. ## Texture Assets @@ -323,6 +356,31 @@ return float4(logo.rgb * alpha, alpha); See `shaders/dvd-bounce/` for a complete texture-driven example. +## Font Assets + +Declare packaged font assets in the manifest: + +```json +{ + "fonts": [ + { + "id": "inter", + "path": "fonts/Inter-Regular.ttf" + } + ] +} +``` + +Rules: + +- `id` must be a valid shader identifier. +- `path` is relative to the shader package directory. +- The file must exist when the manifest is loaded. +- Font asset changes trigger shader reload. +- V1 text layout is single-line; shaders position and scale the generated text texture themselves. + +See `shaders/text-overlay/` for a complete live text example. The sample bundles Roboto Regular and includes its OFL license beside the font file. + ## Temporal Shaders Temporal shaders can request access to previous frames. @@ -401,6 +459,7 @@ These files are ignored by git and are useful for debugging compiler output. If - Do not write a `[shader("fragment")]` entry point in `shader.slang`; the runtime provides it. - Remember enum globals are integer indexes, not strings. - Declare every texture in `shader.json`; undeclared texture samplers will not be bound. +- Declare packaged fonts in `shader.json` when text parameters should use a specific font. - Keep temporal history requests modest. They consume texture units and memory and are capped by runtime config. - If a parameter appears in the UI but not in Slang, the shader may still compile, but the control has no effect. - If a Slang name collides with a generated global, rename your parameter or local symbol. @@ -414,6 +473,7 @@ Before committing a new shader package: - `entryPoint`, parameter IDs, and texture IDs are valid identifiers. - `shader.slang` implements the configured entry point. - Texture files referenced by `textures` exist. +- Font files referenced by `fonts` exist. - Enum defaults are present in their `options`. - Temporal shaders handle short or empty history gracefully. - The app can reload and compile the shader without errors. diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index eb1caf6..cd8f1fa 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -47,7 +47,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -64,6 +67,9 @@ constexpr GLuint kSourceHistoryTextureUnitBase = 2; constexpr GLuint kPackedVideoTextureUnit = 2; constexpr GLuint kGlobalParamsBindingPoint = 0; constexpr unsigned kPrerollFrameCount = 8; +constexpr unsigned kTextTextureWidth = 1024; +constexpr unsigned kTextTextureHeight = 128; +constexpr int kTextSdfSpread = 10; const char* kVertexShaderSource = "#version 430 core\n" "out vec2 vTexCoord;\n" @@ -108,6 +114,156 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE); } +std::wstring Utf8ToWide(const std::string& text) +{ + if (text.empty()) + return std::wstring(); + const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0); + if (required <= 1) + return std::wstring(); + std::wstring wide(static_cast(required - 1), L'\0'); + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required); + return wide; +} + +std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId) +{ + auto valueIt = state.parameterValues.find(parameterId); + return valueIt == state.parameterValues.end() ? std::string() : valueIt->second.textValue; +} + +const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state, const ShaderParameterDefinition& definition) +{ + if (!definition.fontId.empty()) + { + for (const ShaderFontAsset& fontAsset : state.fontAssets) + { + if (fontAsset.id == definition.fontId) + return &fontAsset; + } + } + return state.fontAssets.empty() ? nullptr : &state.fontAssets.front(); +} + +std::vector BuildLocalSdf(const std::vector& alpha, unsigned width, unsigned height) +{ + std::vector sdf(static_cast(width) * height * 4, 0); + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + const bool inside = alpha[static_cast(y) * width + x] > 127; + int bestDistanceSq = kTextSdfSpread * kTextSdfSpread; + for (int oy = -kTextSdfSpread; oy <= kTextSdfSpread; ++oy) + { + const int sy = static_cast(y) + oy; + if (sy < 0 || sy >= static_cast(height)) + continue; + for (int ox = -kTextSdfSpread; ox <= kTextSdfSpread; ++ox) + { + const int sx = static_cast(x) + ox; + if (sx < 0 || sx >= static_cast(width)) + continue; + const bool sampleInside = alpha[static_cast(sy) * width + sx] > 127; + if (sampleInside == inside) + continue; + const int distanceSq = ox * ox + oy * oy; + if (distanceSq < bestDistanceSq) + bestDistanceSq = distanceSq; + } + } + + const float distance = std::sqrt(static_cast(bestDistanceSq)); + const float signedDistance = (inside ? 1.0f : -1.0f) * distance; + float normalized = 0.5f + signedDistance / static_cast(kTextSdfSpread * 2); + if (normalized < 0.0f) + normalized = 0.0f; + if (normalized > 1.0f) + normalized = 1.0f; + const unsigned char value = static_cast(normalized * 255.0f + 0.5f); + const std::size_t out = (static_cast(y) * width + x) * 4; + sdf[out + 0] = value; + sdf[out + 1] = value; + sdf[out + 2] = value; + sdf[out + 3] = value; + } + } + return sdf; +} + +bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector& sdf, std::string& error) +{ + ULONG_PTR gdiplusToken = 0; + Gdiplus::GdiplusStartupInput startupInput; + if (Gdiplus::GdiplusStartup(&gdiplusToken, &startupInput, NULL) != Gdiplus::Ok) + { + error = "Could not start GDI+ for text rendering."; + return false; + } + + Gdiplus::PrivateFontCollection fontCollection; + Gdiplus::FontFamily fallbackFamily(L"Arial"); + Gdiplus::FontFamily* fontFamily = &fallbackFamily; + std::unique_ptr families; + const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring(); + if (!wideFontPath.empty()) + { + 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; + } + + 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; + } + + families.reset(new Gdiplus::FontFamily[familyCount]); + 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; + } + fontFamily = &families[0]; + } + + Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB); + Gdiplus::Graphics graphics(&bitmap); + graphics.Clear(Gdiplus::Color(0, 0, 0, 0)); + graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); + graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + Gdiplus::Font font(fontFamily, 72.0f, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); + Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255)); + Gdiplus::StringFormat format; + format.SetAlignment(Gdiplus::StringAlignmentNear); + format.SetLineAlignment(Gdiplus::StringAlignmentCenter); + format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces); + const Gdiplus::RectF layout(24.0f, 0.0f, static_cast(kTextTextureWidth - 48), static_cast(kTextTextureHeight)); + const std::wstring wideText = Utf8ToWide(text); + graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush); + + std::vector alpha(static_cast(kTextTextureWidth) * kTextTextureHeight, 0); + for (unsigned y = 0; y < kTextTextureHeight; ++y) + { + for (unsigned x = 0; x < kTextTextureWidth; ++x) + { + Gdiplus::Color pixel; + bitmap.GetPixel(x, y, &pixel); + alpha[static_cast(y) * kTextTextureWidth + x] = pixel.GetAlpha(); + } + } + sdf = BuildLocalSdf(alpha, kTextTextureWidth, kTextTextureHeight); + Gdiplus::GdiplusShutdown(gdiplusToken); + return true; +} + std::string NormalizeModeToken(const std::string& value) { std::string normalized; @@ -1488,6 +1644,26 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, } textureBindings.push_back(textureBinding); } + std::vector textBindings; + for (const ShaderParameterDefinition& definition : state.parameterDefinitions) + { + if (definition.type != ShaderParameterType::Text) + continue; + LayerProgram::TextBinding textBinding; + textBinding.parameterId = definition.id; + textBinding.samplerName = definition.id + "Texture"; + textBinding.fontId = definition.fontId; + glGenTextures(1, &textBinding.texture); + glBindTexture(GL_TEXTURE_2D, textBinding.texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + std::vector empty(static_cast(kTextTextureWidth) * kTextTextureHeight * 4, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextTextureWidth, kTextTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, empty.data()); + glBindTexture(GL_TEXTURE_2D, 0); + textBindings.push_back(textBinding); + } const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram.get(), "GlobalParams"); if (globalParamsIndex != GL_INVALID_INDEX) @@ -1517,6 +1693,13 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, 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()); + if (textSamplerLocation >= 0) + glUniform1i(textSamplerLocation, static_cast(textTextureBase + static_cast(index))); + } glUseProgram(0); layerProgram.layerId = state.layerId; @@ -1525,6 +1708,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, layerProgram.vertexShader = newVertexShader.release(); layerProgram.fragmentShader = newFragmentShader.release(); layerProgram.textureBindings.swap(textureBindings); + layerProgram.textBindings.swap(textBindings); return true; } @@ -1626,6 +1810,15 @@ void OpenGLComposite::destroySingleLayerProgram(LayerProgram& layerProgram) } } layerProgram.textureBindings.clear(); + for (LayerProgram::TextBinding& textBinding : layerProgram.textBindings) + { + if (textBinding.texture != 0) + { + glDeleteTextures(1, &textBinding.texture); + textBinding.texture = 0; + } + } + layerProgram.textBindings.clear(); if (layerProgram.program != 0) { @@ -1759,6 +1952,35 @@ bool OpenGLComposite::loadTextureAsset(const ShaderTextureAsset& textureAsset, G return true; } +bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) +{ + const std::string text = TextValueForBinding(state, textBinding.parameterId); + if (text == textBinding.renderedText && textBinding.renderedWidth == kTextTextureWidth && textBinding.renderedHeight == kTextTextureHeight) + return true; + + auto definitionIt = std::find_if(state.parameterDefinitions.begin(), state.parameterDefinitions.end(), + [&textBinding](const ShaderParameterDefinition& definition) { return definition.id == textBinding.parameterId; }); + if (definitionIt == state.parameterDefinitions.end()) + return true; + + const ShaderFontAsset* fontAsset = FindFontAssetForParameter(state, *definitionIt); + std::filesystem::path fontPath; + if (fontAsset) + fontPath = fontAsset->path; + + std::vector sdf; + if (!RasterizeTextSdf(text, fontPath, sdf, error)) + return false; + + 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); + textBinding.renderedText = text; + textBinding.renderedWidth = kTextTextureWidth; + textBinding.renderedHeight = kTextTextureHeight; + return true; +} + void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram) { const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; @@ -1768,6 +1990,12 @@ void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram) glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast(index)); glBindTexture(GL_TEXTURE_2D, layerProgram.textureBindings[index].texture); } + const GLuint textTextureBase = shaderTextureBase + static_cast(layerProgram.textureBindings.size()); + for (std::size_t index = 0; index < layerProgram.textBindings.size(); ++index) + { + glActiveTexture(GL_TEXTURE0 + textTextureBase + static_cast(index)); + glBindTexture(GL_TEXTURE_2D, layerProgram.textBindings[index].texture); + } glActiveTexture(GL_TEXTURE0); } @@ -1798,8 +2026,15 @@ bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector maxAssetTextures) - maxAssetTextures = static_cast(state.textureAssets.size()); + unsigned textTextureCount = 0; + for (const ShaderParameterDefinition& definition : state.parameterDefinitions) + { + if (definition.type == ShaderParameterType::Text) + ++textTextureCount; + } + const unsigned totalShaderTextures = static_cast(state.textureAssets.size()) + textTextureCount; + if (totalShaderTextures > maxAssetTextures) + maxAssetTextures = totalShaderTextures; } GLint maxTextureUnits = 0; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits); @@ -2062,8 +2297,15 @@ void OpenGLComposite::renderEffect() VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); } -void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state) +void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, LayerProgram& layerProgram, const RuntimeRenderState& state) { + for (LayerProgram::TextBinding& textBinding : layerProgram.textBindings) + { + std::string textError; + if (!renderTextBindingTexture(state, textBinding, textError)) + OutputDebugStringA((textError + "\n").c_str()); + } + glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer); glViewport(0, 0, mInputFrameWidth, mInputFrameHeight); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -2086,7 +2328,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati glBindTexture(GL_TEXTURE_2D, 0); } const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap; - for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index) + for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index) { glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast(index)); glBindTexture(GL_TEXTURE_2D, 0); @@ -2223,6 +2465,8 @@ bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state, AppendStd140Int(buffer, selectedIndex); break; } + case ShaderParameterType::Text: + break; } } diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h index c5f74ed..2936f62 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h @@ -167,12 +167,24 @@ private: GLuint texture = 0; }; + struct TextBinding + { + std::string parameterId; + std::string samplerName; + std::string fontId; + GLuint texture = 0; + std::string renderedText; + unsigned renderedWidth = 0; + unsigned renderedHeight = 0; + }; + std::string layerId; std::string shaderId; GLuint program = 0; GLuint vertexShader = 0; GLuint fragmentShader = 0; std::vector textureBindings; + std::vector textBindings; }; std::vector mLayerPrograms; @@ -203,8 +215,9 @@ private: void destroySingleLayerProgram(LayerProgram& layerProgram); void destroyDecodeShaderProgram(); void renderDecodePass(); - void renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state); + void renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, LayerProgram& layerProgram, const RuntimeRenderState& state); bool loadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error); + bool renderTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error); void bindLayerTextureAssets(const LayerProgram& layerProgram); void renderEffect(); bool PollRuntimeChanges(); diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp index b95f6ee..8272b21 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp @@ -133,6 +133,7 @@ std::string ShaderParameterTypeToString(ShaderParameterType type) case ShaderParameterType::Color: return "color"; case ShaderParameterType::Boolean: return "bool"; case ShaderParameterType::Enum: return "enum"; + case ShaderParameterType::Text: return "text"; } return "unknown"; } @@ -179,6 +180,11 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type = ShaderParameterType::Enum; return true; } + if (typeName == "text") + { + type = ShaderParameterType::Text; + return true; + } return false; } @@ -200,6 +206,24 @@ bool TextureAssetsEqual(const std::vector& left, const std:: return true; } +bool FontAssetsEqual(const std::vector& left, const std::vector& right) +{ + if (left.size() != right.size()) + return false; + + for (std::size_t index = 0; index < left.size(); ++index) + { + if (left[index].id != right[index].id || + left[index].path != right[index].path || + left[index].writeTime != right[index].writeTime) + { + return false; + } + } + + return true; +} + std::string ManifestPathMessage(const std::filesystem::path& manifestPath) { return manifestPath.string(); @@ -379,6 +403,49 @@ bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPack return true; } +bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error) +{ + const JsonValue* fontsValue = nullptr; + if (!OptionalArrayField(manifestJson, "fonts", fontsValue, manifestPath, error)) + return false; + if (!fontsValue) + return true; + + for (const JsonValue& fontJson : fontsValue->asArray()) + { + if (!fontJson.isObject()) + { + error = "Shader font entry must be an object in: " + ManifestPathMessage(manifestPath); + return false; + } + + std::string fontId; + std::string fontPath; + if (!RequireNonEmptyStringField(fontJson, "id", fontId, manifestPath, error) || + !RequireNonEmptyStringField(fontJson, "path", fontPath, manifestPath, error)) + { + error = "Shader font is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath); + return false; + } + if (!ValidateShaderIdentifier(fontId, "fonts[].id", manifestPath, error)) + return false; + + ShaderFontAsset fontAsset; + fontAsset.id = fontId; + fontAsset.path = shaderPackage.directoryPath / fontPath; + if (!std::filesystem::exists(fontAsset.path)) + { + error = "Shader font asset not found for package " + shaderPackage.id + ": " + fontAsset.path.string(); + return false; + } + + fontAsset.writeTime = std::filesystem::last_write_time(fontAsset.path); + shaderPackage.fontAssets.push_back(fontAsset); + } + + return true; +} + bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error) { const JsonValue* temporalValue = nullptr; @@ -461,6 +528,17 @@ bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefini return true; } + if (definition.type == ShaderParameterType::Text) + { + if (!defaultValue->isString()) + { + error = "Text parameter default must be a string for: " + definition.id; + return false; + } + definition.defaultTextValue = defaultValue->asString(); + return true; + } + return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error); } @@ -543,6 +621,30 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef return false; } + if (definition.type == ShaderParameterType::Text) + { + if (const JsonValue* fontValue = parameterJson.find("font")) + { + if (!fontValue->isString()) + { + error = "Text parameter 'font' must be a string for: " + definition.id; + return false; + } + definition.fontId = fontValue->asString(); + if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error)) + return false; + } + if (const JsonValue* maxLengthValue = parameterJson.find("maxLength")) + { + if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0) + { + error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id; + return false; + } + definition.maxLength = static_cast(maxLengthValue->asNumber()); + } + } + if (definition.type == ShaderParameterType::Enum) return ParseParameterOptions(parameterJson, definition, manifestPath, error); @@ -693,7 +795,8 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested, } if (previous->second.shaderWriteTime != item.second.shaderWriteTime || previous->second.manifestWriteTime != item.second.manifestWriteTime || - !TextureAssetsEqual(previous->second.textureAssets, item.second.textureAssets)) + !TextureAssetsEqual(previous->second.textureAssets, item.second.textureAssets) || + !FontAssetsEqual(previous->second.fontAssets, item.second.fontAssets)) { registryChanged = true; break; @@ -714,7 +817,8 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested, if (previous->second.first != active->second.shaderWriteTime || previous->second.second != active->second.manifestWriteTime || (previousPackage != previousPackages.end() && - !TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets))) + (!TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets) || + !FontAssetsEqual(previousPackage->second.fontAssets, active->second.fontAssets)))) { mReloadRequested = true; } @@ -1143,6 +1247,7 @@ std::vector RuntimeHost::GetLayerRenderStates(unsigned outpu state.outputHeight = outputHeight; state.parameterDefinitions = shaderIt->second.parameters; state.textureAssets = shaderIt->second.textureAssets; + state.fontAssets = shaderIt->second.fontAssets; state.isTemporal = shaderIt->second.temporal.enabled; state.temporalHistorySource = shaderIt->second.temporal.historySource; state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength; @@ -1447,6 +1552,7 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath, shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath); return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) && + ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) && ParseTemporalSettings(manifestJson, shaderPackage, mConfig.maxTemporalHistoryFrames, manifestPath, error) && ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error); } @@ -1692,6 +1798,12 @@ JsonValue RuntimeHost::SerializeLayerStackLocked() const } parameter.set("options", options); } + if (definition.type == ShaderParameterType::Text) + { + parameter.set("maxLength", JsonValue(static_cast(definition.maxLength))); + if (!definition.fontId.empty()) + parameter.set("font", JsonValue(definition.fontId)); + } ShaderParameterValue value = DefaultValueForDefinition(definition); auto valueIt = layer.parameterValues.find(definition.id); @@ -1830,6 +1942,8 @@ JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition& return JsonValue(value.booleanValue); case ShaderParameterType::Enum: return JsonValue(value.enumValue); + case ShaderParameterType::Text: + return JsonValue(value.textValue); case ShaderParameterType::Float: return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front()); case ShaderParameterType::Vec2: diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.cpp b/apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.cpp index 59de344..006560d 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.cpp @@ -36,6 +36,21 @@ std::vector JsonArrayToNumbers(const JsonValue& value) } return numbers; } + +std::string NormalizeTextValue(const std::string& text, unsigned maxLength) +{ + std::string normalized; + normalized.reserve(std::min(text.size(), maxLength)); + for (unsigned char ch : text) + { + if (ch < 32 || ch > 126) + continue; + if (normalized.size() >= maxLength) + break; + normalized.push_back(static_cast(ch)); + } + return normalized; +} } std::string MakeSafePresetFileStem(const std::string& presetName) @@ -82,6 +97,9 @@ ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& case ShaderParameterType::Enum: value.enumValue = definition.defaultEnumValue; break; + case ShaderParameterType::Text: + value.textValue = NormalizeTextValue(definition.defaultTextValue, definition.maxLength); + break; } return value; } @@ -164,6 +182,14 @@ bool NormalizeAndValidateParameterValue(const ShaderParameterDefinition& definit error = "Enum parameter '" + definition.id + "' received unsupported option '" + selectedValue + "'."; return false; } + case ShaderParameterType::Text: + if (!value.isString()) + { + error = "Expected string value for text parameter '" + definition.id + "'."; + return false; + } + normalizedValue.textValue = NormalizeTextValue(value.asString(), definition.maxLength); + return true; } return false; diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp index dfa41d9..a015ccc 100644 --- a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp @@ -4,6 +4,7 @@ #include "NativeHandles.h" #include +#include #include #include #include @@ -30,15 +31,29 @@ std::string SlangCBufferTypeForParameter(ShaderParameterType type) case ShaderParameterType::Color: return "float4"; case ShaderParameterType::Boolean: return "bool"; case ShaderParameterType::Enum: return "int"; + case ShaderParameterType::Text: return ""; } return "float"; } +std::string CapitalizeIdentifier(const std::string& identifier) +{ + if (identifier.empty()) + return identifier; + std::string text = identifier; + text[0] = static_cast(std::toupper(static_cast(text[0]))); + return text; +} + std::string BuildParameterUniforms(const std::vector& parameters) { std::ostringstream source; for (const ShaderParameterDefinition& definition : parameters) + { + if (definition.type == ShaderParameterType::Text) + continue; source << "\t" << SlangCBufferTypeForParameter(definition.type) << " " << definition.id << ";\n"; + } return source.str(); } @@ -60,6 +75,42 @@ std::string BuildTextureSamplerDeclarations(const std::vector& parameters) +{ + std::ostringstream source; + for (const ShaderParameterDefinition& definition : parameters) + { + if (definition.type != ShaderParameterType::Text) + continue; + source << "Sampler2D " << definition.id << "Texture;\n"; + } + if (source.tellp() > 0) + source << "\n"; + return source.str(); +} + +std::string BuildTextHelpers(const std::vector& parameters) +{ + std::ostringstream source; + for (const ShaderParameterDefinition& definition : parameters) + { + if (definition.type != ShaderParameterType::Text) + continue; + const std::string suffix = CapitalizeIdentifier(definition.id); + source + << "float sample" << suffix << "(float2 uv)\n" + << "{\n" + << "\treturn " << definition.id << "Texture.Sample(uv).r;\n" + << "}\n\n" + << "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n" + << "{\n" + << "\tfloat alpha = sample" << suffix << "(uv) * fillColor.a;\n" + << "\treturn float4(fillColor.rgb * alpha, alpha);\n" + << "}\n\n"; + } + return source.str(); +} + std::string BuildHistorySwitchCases(const std::string& samplerPrefix, unsigned historyLength) { std::ostringstream source; @@ -118,6 +169,8 @@ bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", mMaxTemporalHistoryFrames)); wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", mMaxTemporalHistoryFrames)); 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, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string()); diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.cpp b/apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.cpp index de84943..b3322b7 100644 --- a/apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.cpp @@ -67,6 +67,11 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type = ShaderParameterType::Enum; return true; } + if (typeName == "text") + { + type = ShaderParameterType::Text; + return true; + } return false; } @@ -283,6 +288,49 @@ bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPack return true; } +bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error) +{ + const JsonValue* fontsValue = nullptr; + if (!OptionalArrayField(manifestJson, "fonts", fontsValue, manifestPath, error)) + return false; + if (!fontsValue) + return true; + + for (const JsonValue& fontJson : fontsValue->asArray()) + { + if (!fontJson.isObject()) + { + error = "Shader font entry must be an object in: " + ManifestPathMessage(manifestPath); + return false; + } + + std::string fontId; + std::string fontPath; + if (!RequireNonEmptyStringField(fontJson, "id", fontId, manifestPath, error) || + !RequireNonEmptyStringField(fontJson, "path", fontPath, manifestPath, error)) + { + error = "Shader font is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath); + return false; + } + if (!ValidateShaderIdentifier(fontId, "fonts[].id", manifestPath, error)) + return false; + + ShaderFontAsset fontAsset; + fontAsset.id = fontId; + fontAsset.path = shaderPackage.directoryPath / fontPath; + if (!std::filesystem::exists(fontAsset.path)) + { + error = "Shader font asset not found for package " + shaderPackage.id + ": " + fontAsset.path.string(); + return false; + } + + fontAsset.writeTime = std::filesystem::last_write_time(fontAsset.path); + shaderPackage.fontAssets.push_back(fontAsset); + } + + return true; +} + bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error) { const JsonValue* temporalValue = nullptr; @@ -365,6 +413,17 @@ bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefini return true; } + if (definition.type == ShaderParameterType::Text) + { + if (!defaultValue->isString()) + { + error = "Text parameter default must be a string for: " + definition.id; + return false; + } + definition.defaultTextValue = defaultValue->asString(); + return true; + } + return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error); } @@ -447,6 +506,30 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef return false; } + if (definition.type == ShaderParameterType::Text) + { + if (const JsonValue* fontValue = parameterJson.find("font")) + { + if (!fontValue->isString()) + { + error = "Text parameter 'font' must be a string for: " + definition.id; + return false; + } + definition.fontId = fontValue->asString(); + if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error)) + return false; + } + if (const JsonValue* maxLengthValue = parameterJson.find("maxLength")) + { + if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0) + { + error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id; + return false; + } + definition.maxLength = static_cast(maxLengthValue->asNumber()); + } + } + if (definition.type == ShaderParameterType::Enum) return ParseParameterOptions(parameterJson, definition, manifestPath, error); @@ -544,6 +627,7 @@ bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestP shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath); return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) && + ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) && ParseTemporalSettings(manifestJson, shaderPackage, mMaxTemporalHistoryFrames, manifestPath, error) && ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error); } diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h b/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h index 19fa473..58b2c13 100644 --- a/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h @@ -11,7 +11,8 @@ enum class ShaderParameterType Vec2, Color, Boolean, - Enum + Enum, + Text }; struct ShaderParameterOption @@ -31,6 +32,9 @@ struct ShaderParameterDefinition std::vector stepNumbers; bool defaultBoolean = false; std::string defaultEnumValue; + std::string defaultTextValue; + std::string fontId; + unsigned maxLength = 64; std::vector enumOptions; }; @@ -39,6 +43,7 @@ struct ShaderParameterValue std::vector numberValues; bool booleanValue = false; std::string enumValue; + std::string textValue; }; enum class TemporalHistorySource @@ -63,6 +68,13 @@ struct ShaderTextureAsset std::filesystem::file_time_type writeTime; }; +struct ShaderFontAsset +{ + std::string id; + std::filesystem::path path; + std::filesystem::file_time_type writeTime; +}; + struct ShaderPackage { std::string id; @@ -75,6 +87,7 @@ struct ShaderPackage std::filesystem::path manifestPath; std::vector parameters; std::vector textureAssets; + std::vector fontAssets; TemporalSettings temporal; std::filesystem::file_time_type shaderWriteTime; std::filesystem::file_time_type manifestWriteTime; @@ -87,6 +100,7 @@ struct RuntimeRenderState std::vector parameterDefinitions; std::map parameterValues; std::vector textureAssets; + std::vector fontAssets; double timeSeconds = 0.0; double frameCount = 0.0; double mixAmount = 1.0; diff --git a/runtime/templates/shader_wrapper.slang.in b/runtime/templates/shader_wrapper.slang.in index c6a6557..1b7836a 100644 --- a/runtime/templates/shader_wrapper.slang.in +++ b/runtime/templates/shader_wrapper.slang.in @@ -32,6 +32,7 @@ cbuffer GlobalParams Sampler2D gVideoInput; {{SOURCE_HISTORY_SAMPLERS}}{{TEMPORAL_HISTORY_SAMPLERS}}{{TEXTURE_SAMPLERS}} +{{TEXT_SAMPLERS}} float4 sampleVideo(float2 tc) { return gVideoInput.Sample(tc); @@ -67,6 +68,7 @@ float4 sampleTemporalHistory(int framesAgo, float2 tc) } } +{{TEXT_HELPERS}} #include "{{USER_SHADER_INCLUDE}}" [shader("fragment")] diff --git a/shaders/studio-color/shader.json b/shaders/studio-color/shader.json deleted file mode 100644 index 4e9c6b7..0000000 --- a/shaders/studio-color/shader.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "id": "studio-color", - "name": "Studio Color", - "description": "A built-in sample shader package that demonstrates the runtime parameter contract.", - "category": "Color", - "entryPoint": "shadeVideo", - "parameters": [ - { - "id": "brightness", - "label": "Brightness", - "type": "float", - "default": 1.0, - "min": 0.0, - "max": 2.0, - "step": 0.01 - }, - { - "id": "offset", - "label": "Offset", - "type": "vec2", - "default": [0.0, 0.0], - "min": [-0.2, -0.2], - "max": [0.2, 0.2], - "step": [0.001, 0.001] - }, - { - "id": "tint", - "label": "Tint", - "type": "color", - "default": [1.0, 1.0, 1.0, 1.0] - }, - { - "id": "invert", - "label": "Invert", - "type": "bool", - "default": false - }, - { - "id": "mode", - "label": "Mode", - "type": "enum", - "default": "normal", - "options": [ - { "value": "normal", "label": "Normal" }, - { "value": "luma", "label": "Luma" }, - { "value": "posterize", "label": "Posterize" } - ] - } - ] -} diff --git a/shaders/studio-color/shader.slang b/shaders/studio-color/shader.slang deleted file mode 100644 index bb8b084..0000000 --- a/shaders/studio-color/shader.slang +++ /dev/null @@ -1,23 +0,0 @@ -float4 shadeVideo(ShaderContext context) -{ - float2 uv = clamp(context.uv + offset, float2(0.0, 0.0), float2(1.0, 1.0)); - float4 color = sampleVideo(uv); - - color.rgb *= brightness; - color *= tint; - - if (invert) - color.rgb = 1.0 - color.rgb; - - if (mode == 1) - { - float luma = dot(color.rgb, float3(0.2126, 0.7152, 0.0722)); - color.rgb = float3(luma, luma, luma); - } - else if (mode == 2) - { - color.rgb = floor(color.rgb * 4.0) / 4.0; - } - - return saturate(color); -} diff --git a/shaders/text-overlay/fonts/OFL.txt b/shaders/text-overlay/fonts/OFL.txt new file mode 100644 index 0000000..9c48e05 --- /dev/null +++ b/shaders/text-overlay/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/shaders/text-overlay/fonts/Roboto-Regular.ttf b/shaders/text-overlay/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fa1485fce92852b74189d6be58e73b12d4450095 GIT binary patch literal 157208 zcmb4r2S8KD|M=Y-340G2l0XDh_Fe*EgNQPfy{EE_2=0wy+~Qyeh@!X&s0gSiwzb+; zZS5Y`4%=^Q?cPd~|L0zUwd?o&{vr47y}NsNpFQrq1Yv}bAsiTF5EB^{-D`gM6vEFx zK!{3@Nt}}sS+~yz;TLBh)bvwKN?Q0UpU#?(s6=;!Ouv|u;^w)3eg8&;a0|?mUsP34 zYj^McJcP_-2=R^<)z=AwesfrfkQ-bhMQLqW)iHL1Cqk3c5z^mSR1+I%O3q zhV_R?f32jXc4_+U*Aa652=Kp+2yJNk?N?uK?9S7l@iVg0fChw~|IW~r)Sqojw0UeP ze^b*#V-Z|xB8s%aAKb^UROmp{%*U3Bnb5Cle|ltH$rv~uN2aJA@!?Jbtz-lYtt^UK~Fh3N^0CiJ4(Prjr)WEDp)$9kTf_aQeaVaXNE~Aaq zP2@;RkToMkvvD(;O!*=Q${OvY6Ob{CUj*eOlw2rzP<)`YL1~0C8_Gf`#rQkai2Knr z#t_9a#i)gGL!0OuD4y?zs#t$CnHfjNSs%276{F+KRw$Kly$CI1&!gj%3%bC}M?NeI z?Q_s^9vKVm>{`^o>Tq>6^9}S}MM=zccGMYyTP%|Be zra`Smrz0_a05Q~36u@MnDrObh%DAH(WYubm~ylY|A4mRhsse#k2^oW zXV4aA0oumwMA_VZTTvc02-mhycOp%u0&S*0Lbf~_InlqN>GVxx26YB?9*OZ!NCveD zyAZ91^DRsP(7G380X`LsH`2$C&>rRsltpKt5?&n2ruP8MW2l6wMyq)4zwiGDmxWr*+W2obG{g6_o)$(|A58kr#k!>6cI>f0^=o zCJJQ``Y|J@m$`_#y#S2lA1?PYGDJhmO#;3wz+Q-|7t_9ALJ60uv* zAsDlp-;FBxk*EOH>t;8hTxvx5DZ3h#GR-KP-3j#k5pAXzu5PO4z$Zes}UOSMMz!^wG`?cs2!p9r}WSeqk|*_KV}gcV0(~| zl>ly`$P-{*0`wbzI-0$RX0w}7CYy{T%tlnqTt?jtjobj<0wxKr?Sa<-JF^4UY(dX4 zHOPT}1Ten0p5BP!k$AiRvR7WG02M_fvi9t6A4`G3FOXdpah-^N@d?eee5!{pT7yo_*apP zXM_xSU%?m_9ftY#@Ey?wkljqC3Kaq!JLzno`y0rWXN$}j1C-8mq7Ehn%0cAJev3Mp z4mgK4`du`Yy$$${!gVGp=PgAYye`1!S7gKUg>mUnW}{+W6V$~(k8=VQ8W7q+ONr?3;fqW zHm0CN`h7Hu@B?%=8)#t0FvyL&23g``m~#qRH8D5qiRzhHfK`F&fWM_sYJtZkPkZc58S5y?;Wxfo=^8^LTqvChs%g!#w26IwKEeCQ4y6&{F<26veAYq5LQ41n&^q31jOS z8&t}kK~q>;kn0k(pGU#kKf>BhphLE(mN7v{V;Wd@bxjR}E!P5@o(*lFNt8RBAA&;c z{c9?tPkDpwh5~l>5HA#L_)(;b+dzlgkUMTcQ=pzhy@4vI56}$i8kz!jHyZ5RJnAgk z0y?~h%>?_h8YS~jfUPM*l~6M2!!Y(+lmx{WN;Z_aP=cV$fijc!M~T#Ls)D~qsZbK> z0<;YJ$UV!UsO;GP$Ndc0Ox|21;O#=a8uh^6ui)GXWCVN==$L>JAt$g|wrniQVdnsE zzW`f!8*#|f`qhnwbLScLnrK{{? z)`49he#;R2f*aZZJaeXIq4VH3YbXZ%#*ffmgM`c$6wG@=Rlp|&^V$)_MZhO-l%?Ur z(T3nka83e!o(rV_N;snr_R0YC&H!Y>0Bo_Uv_f43xWqC}NRLScT{VUNa%94+M2i_m zuvtPB2{I&QE&=>G;7=~0eCAWM1!QFleIBmUpuU5g@s|Mm1Gqkgis@INCii(G54s!a z(S2}D<|Ow*O>aga%pJJzJ0xbyPyuTWF#d?@!5=#^D^LMH98KjXqAhG4tbZHs&4hD% zuuhvBcu(s zRlqC-|E&eZ4`ly2fGH1fKL)rL!ul`|lr8M@hytIU%dJP?Cb&F65gK+d*GV)L=o<>< zpXG5s_|WsHi~bzO2EuyJqhfXz*x)Ik(Pyh1LgKRFcCk5z)Neo7HDt{tz-MyZ-vBl1gB>T(Byfb$v&v>KpA-&&ykq!*#fZ~ ziRUH?7t29BNAM!+{jDZ=!FnW?{l^;OTh;<%OoB6k=fBh>1}41t*Batr(AjN3uR*}0 z9qL<94?z70>eqmt=J*HYW2jF+40D100Br~T@gcIo#nTYef*u~iKY+Z04}*Fxsi`LL z>!iMo4iG*N`uw$K7AP-rF(lCIuQiFsxtLg0r>L)sAtp)!`OEvSH77$5OFvs*2fG1$ zC%pJyYmx(yIGo4`;T*_y<~Bo5|qAu;e8(zEMKoD;kLrvkPf)@_Dz z0?JK*g@v+-24BGWg!5=U@edP)gO9-YS2&4pnJC0(JX;7|Cd#uoJaf(?{)*s43h`s= z!s*R-%B?&LWgE3id63)BG}IMt$T=lUVfVkQYMk!a%+;VaGU{LLNov!H3+205VB9glhx)VD5t%iIGXj@M5C@$B59DV}df3B4`|GE7i z?D=2to49WRzkjcl-!LJ{p?~Wqv?D$l>K@?r#=qLnXRaasR$hDNT1BG?T218J|E>P3 z-P{Rw6!PMz zmw=vgx~%eb>!12IweR9$l}Ttau{n@;aP@=^Pxv7AMa2@%?%OH5SfTPFWv{%-Tvy&_ z%9Lr0T=^^42k{%R_nZyo>^sEt5Z{wn|Eb+3IRLTA>U@FoJ#fzX2@>an{U*5pX`k=~ z&?b61KZ#?>8W6*C^OBr__^Ufmlevj6BJ)Eo!o}XCpR~amTue@4e-iVPykQ7(EQlGo z`(a(;3nmITUaebCbp-q#SsV0XBl9NMhBr|&&lpvcb4Z~mC^QrkIFgjs1TnnbP>-WE zh=*3d8HIGwDs&K?LZ{IcbRFG6pQCS)0_$T#Y>yqWJ5IrQxD2nttMLxJ8+T(F{thcC zE6R_`pe|7_Qa7o4)Q>c#X3}~Mha_%+l4*C6T(x%e&LAlvT)3nwKcXiv$eBzuywV~wWI8KcKUWkc9wQF zc20J&cKLQC_6O}>w!iZY^~-Zl7==>csNm_h{U1tcjRx&Z_3Zf zkI8%GhvXe{*n32l@Pq>XkH* zkI{n#9}RzW>Z21Mb=}|d(VmaW@4xWjsr$qChwk^^KXd=k{f+l)?pNO5g3$deZ6?o$_mw!6Nuqy|5uD~sDl&QM#4G=H` zDE?3~q0EP}56U4ZKv@c?O+Eb6G*2(6cb=X;b3&0Bq5SWEu($rFKdPNNKy^@^R2Ov+ zt)=!;C#h4^Y3dBsN68>dUr+T@1JqgS9OU{NP$M-+U7&`jVQPfBh&G~4)OFDJ8zA#9 zQ7@w=*r|Snx<%cl?oh8HDPXn*ZH2gT8>Iw&z%&Iq!NBe{OY>+xtpWO>MQhVKXb0?| z>C$>&D-38u+K4trus=zg(5AE*ZB9?3El@MsLr_t5+31NtrS6Zh!1>38UN z>G$aO=@008=tFd${*bBaEm0T1E3tP4WonXN7R_iftP zxMBUewQE+dTDfBRvZYHJ>KE57k}RyPsjjMAFu$U_thA)KsIVYEFE=MUD>EZ~-rTg* zl%&KtvlC{;&y0(WiJmGj(9(3q{n}a)qKFbL7iZM3r46UrF3woSi;(fTM%f%Ep-h~V zZX2JR9u;Y2Ynx#uvXzNtjC~X-1;x^$iH;1I0`7u)VT$+^QG8Nnx-d$b&)o$rseigs z_4|_uPOPz)DXNo_^8!ixIz+D#s7F?TZ(6AV6deIQ2YBS)*^*fM-pofAUM%5$cdZ3Mg zHVswV8rA5j>d_j|V?cU_5ryHYBY|1<2UG$^inUm=hFDXqL+Mh$3(_nx}05IT`lMM6jd^@w7iMNXtw=s{;=n zthghL2q%9o&Qbl;Y$wqlVx5!@42vz_umwBzBDwvQ5mSj)sI>11ra70A48Mn+8bnK>TUb;;MkBL+ini~^j6DuPAwVgfj!a8b5-!sgg+J{HMWL!59_itW z!e!V@Mdv6{lyFjov`AD4GAB+49Mhg4Ij6*?D0lAK}1x=T#yF`(ZL`^5|h!RR=;)0@l7!j4h4R&z` z5TsF}0%0+j3qTiWl_G-E%uKRMYG%4rr&v@h0-B1&(gHx+N?4R(CCw<}Xbj*0XvoEx z{lgxq%@IYk-oB_54xk8y`JzHq1JTF7ww3+0p%g|v+a{VxmIGjC5~!h;&J;xz!w6Ce zie)s&ovpAqLnS0Ak+T&4ItZo^fk<$Jguqj7a;ZTOkrZ+T{oEB?z(*=8`}1Y_)5~b+ z24P_AT~)FoV;qRArQ6EpTgfUjh`gwV6v);T3Z+5=QJ{z%I5~@f-k5wDYab(9Q&a$W zfE5DSgNB*VAWSa=>4gcS^Q99K2e&Z}Pgjyv1Mi=i5{xKLg{3KbLIv5HL}7l0Fh3t! z!424231uwQ!qNgFT*S~O!us$@1Zxjffiwl~MMPz+WPGssr3ED-TQKL)mch}LV;`(O z6C@;EhODGgkyM5OMEhtM4AVQvcn)!-f={iJsGtOxNY)b;lyLV(1GpS@2z*viBHIiY zLfI3#6T*RgC?tm>DM+I%2egf~H@)EO$RlL7=H}>d)cZ|Iq@C_Oco$)nE;7R)8!yU6x1|B_BTbfs=(!GRoW^ zIthbtGWZj)3cx5rJk~xA$SVfXvm$p3WfV9s&SyK?`STiJt^dq5?r% z1+QvR9+_XJ!+kXEWg7OtXc+@AsJi(CoryWQa{yX(4}b*Vst|y4Sc-Ty^5GE4S6?vp zKz5jqbA|)~!g+B4XnldmYDjrC5j=1{T;+>0GRX4q0kI=@a8pZF6BBa5%o_hl3iV2= z#kK8a+AxZsplVPZwd`f^0i*zC(x)OEECEd%yks3U^#Cez1(f6H#*vQPK-+I3i~uE7 z>s^VJtUSZ1STz|>RmBOxqJrfvO5);+EYJp#Eg$R{Pyh^?P?q8ZZiB;Z6L*ILh&Zth zL@IH#2t|VosZWp@k|9wns>*HKsfn4VdrmcS$w=x5YpdIBv=`IY5Xta?l`v{rgg|K|&Zb zlPmxgr9xvSL4^fEgHB7OMFkL3<>(O&)p0O{b|b(k08k51;|8=@0eB~p)d2=Tn7d3( zC+KT~5OBhy4IKtR$1#;?YeT02AU*~LO%3;uQb`D?9<3rB&==<85xbKy31X>a z5)VVnFES9~8EA$o5fZ5+E(Lr5W1l|5Dj)+gi6P~e*2G=`X(({=P*SBrp@AWENrgs` zTF4rSDl+Om5!VJ@oadk(MKEY|l1f$MNEEI^N#j$12qZ_-^0(3gn4V@_El&SeH#nz= zRIpb=s9dzfme67d<%*UWM|Jd=)eSk+mQ04HA$8JSaTK`*u8h@pr+Amj7<#$U^ z>8BJd@N*H&_nRcB@Ky@SJ(Yqo&s&00FQuTwLn$bBR|<;UPYMd%%LE0kNs3r65MA6hsRpf+$<1AktPM zh!7|R;Wo{JFdL;nJXt9SwN?s(gPO$;1v7%Y1%W<#f&gEMz~6U{z|YrC;Oo;Y@bVN3 zJlrJ$H`kd0r|DvWqXi>yu%9Zh_p-9co?>Yv5V4jP*>)C6fvqrDAoQPPmMxg%BCs)6 z3MQK=1=ePKft8nqb=D+rGwUo%a%N7>Of5ppj%FHr7^N9{7^E3x7-Z;r>!h*XOqxyx zlObU87)qa6&3wzy`nh^(+TL1ed~aSF_CRTR8Cn^d;nAVIJl<+}YMM^XCJYU*3qmJ^Fv*n8P2OLdAL;wzKmWtxduGTbDy zPa#K0ab&zE8A{8{O7F*bdq(57ZD?|MyliJm`T#skm<(w=cDLKBNmy;17)dR0Y{ulyO>P2u`0gwSPl6~SAfXawzte@E_WBf5l6LQ5NZ z9i2t3_#?EByE=}wVW;#AI*7KSy|9~j9GSz8WgA+Kw!m|Q54rXZG^n1X2lh=5!90WT zbOqi=Lfc{FAv6GU?n15b)Zr`|#W%5>n>z-z01T@6TTvIxEJJHiBh0%8&d;d*?9_H!tym+&Db-Wk&Rs0?eMk7{ZqsE}d zeN9)*xtjAe_iEnI{9MaIYlc>n)WtmQ(>Cd=!VcP&4){K<;7O0zm-b=BI!I@!8!a>C>@lRvWwu-R_&P+%o+6BG!f zg5!eE;JHc%JU5zU+h+Tv-Aub~yN^WEMTw#|(Xi-k(fuiSiuIIPQyQk6o$}fgxxJyi zy}he_hJC&LPW!|5AKHIzFL!Wu*x+!|;jY8Cjv~i;$Foz7rmmR!;WV9T_0v9>?lQe> z`nBm_I0ZSKbb8?I>Ab@EjOfv-?-0o-|haI`_CT!9#W6D zJcB*+JzG5A^m6c;-Uwv zrGL18x&MZMNde-3x`5VzmjX2cV**zMz7#Yys66Po8HO_|XPlq$bFgJ_P4EjLOh``1 zg^(XYokCZJz9M!L?+9bVmWTZoz9{_N2&agGh}H;2WKI;0>WS8gUK~9X{d>%`n3xzz z%%+%*SfAL9v4>(uV;{!_#~q4$KJN3GwllM5*34WtbNkHhnfK$ZSt+TX$R9D%?+BnY3^(DjOL}xyEyN~ zd9SB?r)U2k|HbK*=_}K>ryondmVPh&*9^mq$r;l!(lY8YIx|Kxu4lZJ@m*$5=8eoR zGL=~xS%z7bS)wfGET629temXstfs7itb5r;*;d(8vR$%$vqQ6Evy-wjvx~ATvlnNt z%HEWHCi~4C$DG`p!JKQkmbv-41G(dQv+}m&y_iqsugQO@z^S0P;8el2g4YYaEKn8} z7p^MoFT7Uxb{e12DS@XN*zqnx9f}#b>7mO|Vy3(dHu5wFdU*-FizgDGGHCKI6ZCRaP zy{1}L{c`n_8c~f;O?FLB&8^zd+O4%WYac8$Sy;Sq)56mWUzPAA$&y`??-uzjTEFPs zy2*7Jbxn28E!J9`wfM^7hxOL=srB3Iuhf6vAZW;L*wb*Z;ngLRmn>Owb;%b?BbKgN zI=b}NWx31dFFUmC{pFU+0KA*Hv>@HLv<`)vv2_Rv%kE zy!wYVwrj4gwOm`f_VC&_*RkvT*DYFie!a>1$n|;arR%S+|7wHLhQtkfH;iuhveCCO zv@y0ZsWG#$sIjtfapR?pIve9Q?%nv^Cf!Z5Hf`N>cGGy1c~e=_lBO>O_QCnNLe!b0i zTgSGC+kV)dyS-`qn>#dire6y;Ju3 z?Ty=;y|-rX3;PoG#EjWt%qCBx87)dqxGZKAKQ3s zmTj(WVQr~x)3vT^Pgif(NY|~dkGdXp{dSN( zXm)Vg!JvZ)2MZ1^KDhSawu1)`o;~=&!FLWmJUHI1-!1C)?T+it?yl)x*S)X%c=x65 z+ufgZ|9psl$nwyFL#qz$JaqWb;Gr9b-aGW@kn(WS;iAKf4>uidKiqft+Tk}3e{uLp z53grZk5i9dPh3w3w`m_n6?A*Rh~u5y#?>r5wvTR KvAxHR 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 literal 0 HcmV?d00001 diff --git a/shaders/text-overlay/shader.json b/shaders/text-overlay/shader.json new file mode 100644 index 0000000..b90f4fa --- /dev/null +++ b/shaders/text-overlay/shader.json @@ -0,0 +1,62 @@ +{ + "id": "text-overlay", + "name": "Text Overlay", + "description": "Single-line live text overlay using the runtime text SDF helper functions.", + "category": "Scopes & Guides", + "entryPoint": "shadeVideo", + "fonts": [ + { + "id": "roboto", + "path": "fonts/Roboto-Regular.ttf" + } + ], + "parameters": [ + { + "id": "titleText", + "label": "Text", + "type": "text", + "default": "VIDEO SHADER", + "font": "roboto", + "maxLength": 64 + }, + { + "id": "position", + "label": "Position", + "type": "vec2", + "default": [0.08, 0.12], + "min": [0.0, 0.0], + "max": [1.0, 1.0], + "step": [0.001, 0.001] + }, + { + "id": "scale", + "label": "Scale", + "type": "float", + "default": 0.42, + "min": 0.1, + "max": 1.5, + "step": 0.01 + }, + { + "id": "fillColor", + "label": "Fill", + "type": "color", + "default": [1.0, 1.0, 1.0, 1.0] + }, + { + "id": "outlineColor", + "label": "Outline", + "type": "color", + "default": [0.0, 0.0, 0.0, 0.8] + }, + { + "id": "outlineWidth", + "label": "Outline Width", + "type": "float", + "default": 0.12, + "min": 0.0, + "max": 0.5, + "step": 0.01 + } + ] +} diff --git a/shaders/text-overlay/shader.slang b/shaders/text-overlay/shader.slang new file mode 100644 index 0000000..abfdebb --- /dev/null +++ b/shaders/text-overlay/shader.slang @@ -0,0 +1,28 @@ +float alphaOver(float baseAlpha, float overAlpha) +{ + return overAlpha + baseAlpha * (1.0 - overAlpha); +} + +float4 compositeOver(float4 baseColor, float4 overColor) +{ + float outAlpha = alphaOver(baseColor.a, overColor.a); + float3 outRgb = overColor.rgb + baseColor.rgb * (1.0 - overColor.a); + return float4(outRgb, outAlpha); +} + +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)); + + float mask = sampleTitleText(textUv); + float fill = smoothstep(0.48, 0.54, mask); + float outline = smoothstep(0.48 - outlineWidth, 0.54 - outlineWidth, mask); + + float4 base = context.sourceColor; + float4 outlineLayer = float4(outlineColor.rgb * outlineColor.a, outline * outlineColor.a); + float4 fillLayer = float4(fillColor.rgb * fillColor.a, fill * fillColor.a); + return saturate(compositeOver(compositeOver(base, outlineLayer), fillLayer)); +} diff --git a/tests/RuntimeParameterUtilsTests.cpp b/tests/RuntimeParameterUtilsTests.cpp index 7a6ea57..b3a7a9a 100644 --- a/tests/RuntimeParameterUtilsTests.cpp +++ b/tests/RuntimeParameterUtilsTests.cpp @@ -100,6 +100,26 @@ void TestEnumAndDefaults() error.clear(); Expect(!NormalizeAndValidateParameterValue(definition, JsonValue("other"), value, error), "enum rejects unknown options"); } + +void TestTextNormalization() +{ + ShaderParameterDefinition definition; + definition.id = "titleText"; + definition.type = ShaderParameterType::Text; + definition.defaultTextValue = "DEFAULT"; + definition.maxLength = 6; + + ShaderParameterValue defaultValue = DefaultValueForDefinition(definition); + Expect(defaultValue.textValue == "DEFAUL", "text default is clamped to max length"); + + ShaderParameterValue value; + std::string error; + Expect(NormalizeAndValidateParameterValue(definition, JsonValue("ABC\tDEF\x01GHI"), value, error), "text accepts string values"); + Expect(value.textValue == "ABCDEF", "text drops non-printable characters and clamps length"); + + error.clear(); + Expect(!NormalizeAndValidateParameterValue(definition, JsonValue(12.0), value, error), "text rejects non-string values"); +} } int main() @@ -108,6 +128,7 @@ int main() TestFloatNormalization(); TestVectorNormalization(); TestEnumAndDefaults(); + TestTextNormalization(); if (gFailures != 0) { diff --git a/tests/ShaderPackageRegistryTests.cpp b/tests/ShaderPackageRegistryTests.cpp index 1804dab..d0c3bf0 100644 --- a/tests/ShaderPackageRegistryTests.cpp +++ b/tests/ShaderPackageRegistryTests.cpp @@ -47,6 +47,7 @@ void TestValidManifest() { const std::filesystem::path root = MakeTestRoot(); WriteFile(root / "look" / "mask.png", "not a real png, but enough for existence checks"); + WriteFile(root / "look" / "Inter.ttf", "not a real font, but enough for existence checks"); WriteShaderPackage(root, "look", R"({ "id": "look-01", "name": "Look 01", @@ -54,9 +55,11 @@ void TestValidManifest() "category": "Tests", "entryPoint": "shadeVideo", "textures": [{ "id": "maskTex", "path": "mask.png" }], + "fonts": [{ "id": "inter", "path": "Inter.ttf" }], "temporal": { "enabled": true, "historySource": "source", "historyLength": 8 }, "parameters": [ { "id": "gain", "label": "Gain", "type": "float", "default": 0.5, "min": 0, "max": 1 }, + { "id": "titleText", "label": "Title", "type": "text", "default": "LIVE", "font": "inter", "maxLength": 32 }, { "id": "mode", "label": "Mode", "type": "enum", "default": "soft", "options": [ { "value": "soft", "label": "Soft" }, { "value": "hard", "label": "Hard" } @@ -70,8 +73,29 @@ void TestValidManifest() Expect(registry.ParseManifest(root / "look" / "shader.json", package, error), "valid manifest parses"); Expect(package.id == "look-01", "manifest id is preserved"); Expect(package.textureAssets.size() == 1 && package.textureAssets[0].id == "maskTex", "texture assets parse"); + Expect(package.fontAssets.size() == 1 && package.fontAssets[0].id == "inter", "font assets parse"); Expect(package.temporal.enabled && package.temporal.effectiveHistoryLength == 4, "temporal history is capped"); - Expect(package.parameters.size() == 2, "parameters parse"); + Expect(package.parameters.size() == 3, "parameters parse"); + Expect(package.parameters[1].type == ShaderParameterType::Text && package.parameters[1].defaultTextValue == "LIVE", "text parameter parses"); + + std::filesystem::remove_all(root); +} + +void TestMissingFontAsset() +{ + const std::filesystem::path root = MakeTestRoot(); + WriteShaderPackage(root, "bad-font", R"({ + "id": "bad-font", + "name": "Bad Font", + "fonts": [{ "id": "missingFont", "path": "missing.ttf" }], + "parameters": [] + })"); + + ShaderPackageRegistry registry(4); + ShaderPackage package; + std::string error; + Expect(!registry.ParseManifest(root / "bad-font" / "shader.json", package, error), "missing font asset is rejected"); + Expect(error.find("font asset not found") != std::string::npos, "missing font error is clear"); std::filesystem::remove_all(root); } @@ -115,6 +139,7 @@ void TestDuplicateScan() int main() { TestValidManifest(); + TestMissingFontAsset(); TestInvalidManifest(); TestDuplicateScan(); diff --git a/ui/src/components/ParameterField.jsx b/ui/src/components/ParameterField.jsx index 6d392ec..946815a 100644 --- a/ui/src/components/ParameterField.jsx +++ b/ui/src/components/ParameterField.jsx @@ -273,5 +273,22 @@ export function ParameterField({ layer, parameter, onParameterChange }) { ); } + if (parameter.type === "text") { + return ( +
+ {header} + sendValue(event.target.value)} + onBlur={endInteraction} + /> + +
+ ); + } + return null; } -- 2.49.1 From 62c3ded1f868c5e68b9fc04c8f4f4a0c8c088226 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 5 May 2026 23:47:08 +1000 Subject: [PATCH 2/4] Font working --- README.md | 3 + .../OpenGLComposite.cpp | 154 +++++++++++++++--- .../OpenGLComposite.h | 1 + .../RuntimeHost.cpp | 56 ++++++- .../ShaderCompiler.cpp | 11 +- image.png | Bin 0 -> 48830 bytes shaders/text-overlay/shader.slang | 9 +- ui/src/components/ParameterField.jsx | 1 + ui/src/hooks/useThrottledParameterValue.js | 21 +-- 9 files changed, 220 insertions(+), 36 deletions(-) create mode 100644 image.png 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 0000000000000000000000000000000000000000..f8ceba4c09059daa70025f65e845d7eaaa4be5a3 GIT binary patch literal 48830 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 literal 0 HcmV?d00001 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 () => { -- 2.49.1 From 6ce09c0e9c3699606968ed684b78111d646570c6 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 5 May 2026 23:51:02 +1000 Subject: [PATCH 3/4] making text pretty --- .../OpenGLComposite.cpp | 62 +++++++++++++++++-- shaders/text-overlay/shader.slang | 8 ++- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index cfa3a2c..4bea15e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -68,9 +68,12 @@ constexpr GLuint kSourceHistoryTextureUnitBase = 2; constexpr GLuint kPackedVideoTextureUnit = 2; constexpr GLuint kGlobalParamsBindingPoint = 0; constexpr unsigned kPrerollFrameCount = 8; -constexpr unsigned kTextTextureWidth = 1024; -constexpr unsigned kTextTextureHeight = 128; -constexpr int kTextSdfSpread = 10; +constexpr unsigned kTextTextureWidth = 2048; +constexpr unsigned kTextTextureHeight = 256; +constexpr int kTextSdfSpread = 20; +constexpr unsigned kTextSdfBlurPasses = 1; +constexpr float kTextFontPixelSize = 144.0f; +constexpr float kTextLayoutPadding = 48.0f; const char* kVertexShaderSource = "#version 430 core\n" "out vec2 vTexCoord;\n" @@ -230,6 +233,48 @@ std::vector FlipTextTextureForShaderUv(const std::vector BlurTextSdf(const std::vector& pixels, unsigned width, unsigned height, unsigned passes) +{ + std::vector current = pixels; + std::vector next(pixels.size(), 0); + for (unsigned pass = 0; pass < passes; ++pass) + { + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + unsigned weightedTotal = 0; + unsigned weightSum = 0; + for (int oy = -1; oy <= 1; ++oy) + { + const int sy = static_cast(y) + oy; + if (sy < 0 || sy >= static_cast(height)) + continue; + for (int ox = -1; ox <= 1; ++ox) + { + const int sx = static_cast(x) + ox; + if (sx < 0 || sx >= static_cast(width)) + continue; + const unsigned weight = (ox == 0 && oy == 0) ? 4u : ((ox == 0 || oy == 0) ? 2u : 1u); + const std::size_t sample = (static_cast(sy) * width + sx) * 4; + weightedTotal += static_cast(current[sample]) * weight; + weightSum += weight; + } + } + + const unsigned char value = static_cast((weightedTotal + weightSum / 2) / weightSum); + const std::size_t out = (static_cast(y) * width + x) * 4; + next[out + 0] = value; + next[out + 1] = value; + next[out + 2] = value; + next[out + 3] = value; + } + } + current.swap(next); + } + return current; +} + void WriteTextMaskDebugDump(const std::string& text, const std::vector& alpha, const std::vector& sdf, unsigned width, unsigned height) { try @@ -338,15 +383,19 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); graphics.Clear(Gdiplus::Color(255, 0, 0, 0)); graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver); - graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); + graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias); graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); - Gdiplus::Font font(fontFamily, 72.0f, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); + Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255)); Gdiplus::StringFormat format; format.SetAlignment(Gdiplus::StringAlignmentNear); format.SetLineAlignment(Gdiplus::StringAlignmentCenter); format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces); - const Gdiplus::RectF layout(24.0f, 0.0f, static_cast(kTextTextureWidth - 48), static_cast(kTextTextureHeight)); + const Gdiplus::RectF layout( + kTextLayoutPadding, + 0.0f, + static_cast(kTextTextureWidth) - (kTextLayoutPadding * 2.0f), + static_cast(kTextTextureHeight)); const std::wstring wideText = Utf8ToWide(text); graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush); @@ -366,6 +415,7 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font } } sdf = BuildLocalSdf(alpha, kTextTextureWidth, kTextTextureHeight); + sdf = BlurTextSdf(sdf, kTextTextureWidth, kTextTextureHeight, kTextSdfBlurPasses); sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight); WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight); return true; diff --git a/shaders/text-overlay/shader.slang b/shaders/text-overlay/shader.slang index 118d47e..98dc8c2 100644 --- a/shaders/text-overlay/shader.slang +++ b/shaders/text-overlay/shader.slang @@ -20,8 +20,12 @@ float4 shadeVideo(ShaderContext context) bool insideTextRect = textUv.x >= 0.0 && textUv.x <= 1.0 && textUv.y >= 0.0 && textUv.y <= 1.0; 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 edge = 0.5; + float aa = max(fwidth(mask) * 1.5, 0.006); + float outlineAmount = min(outlineWidth * 0.25, 0.24); + float fill = smoothstep(edge - aa, edge + aa, mask); + float outlineField = smoothstep(edge - outlineAmount - aa, edge - outlineAmount + aa, mask); + float outline = saturate(outlineField - fill); float textAlpha = max(fill * fillColor.a, outline * outlineColor.a); if (textAlpha <= 0.0001) return context.sourceColor; -- 2.49.1 From 7e4ab5cbd805108e0cc5652f7d347bc2d30959e2 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 5 May 2026 23:57:02 +1000 Subject: [PATCH 4/4] V1 text, needs improvements --- README.md | 4 +-- .../OpenGLComposite.cpp | 23 ++++++++++++++++- shaders/text-overlay/shader.json | 9 +++++++ shaders/text-overlay/shader.slang | 25 +++++++++++++------ 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 809f01d..c056681 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ If your Windows runner stores the Blackmagic SDK outside the repo, configure `GP ## Still todo Audio -Fonts +improve text rendering genlock Logs anamorphic desqueeze @@ -240,6 +240,6 @@ 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 +runtime date time UTC and offset from PCs internal clock 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 4bea15e..7a03279 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -205,6 +205,9 @@ std::vector BuildLocalSdf(const std::vector& alpha const float distance = std::sqrt(static_cast(bestDistanceSq)); const float signedDistance = (inside ? 1.0f : -1.0f) * distance; float normalized = 0.5f + signedDistance / static_cast(kTextSdfSpread * 2); + const unsigned char sourceAlpha = alpha[static_cast(y) * width + x]; + if (sourceAlpha > 0 && sourceAlpha < 255) + normalized = static_cast(sourceAlpha) / 255.0f; if (normalized < 0.0f) normalized = 0.0f; if (normalized > 1.0f) @@ -220,6 +223,24 @@ std::vector BuildLocalSdf(const std::vector& alpha return sdf; } +std::vector BuildTextCoverageTexture(const std::vector& alpha, unsigned width, unsigned height) +{ + std::vector coverage(static_cast(width) * height * 4, 0); + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + const unsigned char value = alpha[static_cast(y) * width + x]; + const std::size_t out = (static_cast(y) * width + x) * 4; + coverage[out + 0] = value; + coverage[out + 1] = value; + coverage[out + 2] = value; + coverage[out + 3] = value; + } + } + return coverage; +} + std::vector FlipTextTextureForShaderUv(const std::vector& pixels, unsigned width, unsigned height) { std::vector flipped(pixels.size(), 0); @@ -414,7 +435,7 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font alpha[static_cast(y) * kTextTextureWidth + x] = static_cast(luminance); } } - sdf = BuildLocalSdf(alpha, kTextTextureWidth, kTextTextureHeight); + sdf = BuildTextCoverageTexture(alpha, kTextTextureWidth, kTextTextureHeight); sdf = BlurTextSdf(sdf, kTextTextureWidth, kTextTextureHeight, kTextSdfBlurPasses); sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight); WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight); diff --git a/shaders/text-overlay/shader.json b/shaders/text-overlay/shader.json index b90f4fa..e5b4e52 100644 --- a/shaders/text-overlay/shader.json +++ b/shaders/text-overlay/shader.json @@ -57,6 +57,15 @@ "min": 0.0, "max": 0.5, "step": 0.01 + }, + { + "id": "softness", + "label": "Softness", + "type": "float", + "default": 0.04, + "min": 0.0, + "max": 0.3, + "step": 0.01 } ] } diff --git a/shaders/text-overlay/shader.slang b/shaders/text-overlay/shader.slang index 98dc8c2..1f627cc 100644 --- a/shaders/text-overlay/shader.slang +++ b/shaders/text-overlay/shader.slang @@ -20,18 +20,27 @@ float4 shadeVideo(ShaderContext context) bool insideTextRect = textUv.x >= 0.0 && textUv.x <= 1.0 && textUv.y >= 0.0 && textUv.y <= 1.0; float mask = insideTextRect ? sampleTitleText(textUv) : 0.0; - float edge = 0.5; - float aa = max(fwidth(mask) * 1.5, 0.006); - float outlineAmount = min(outlineWidth * 0.25, 0.24); + float edge = 0.02; + float aa = max(fwidth(mask) * 1.5, 0.002); float fill = smoothstep(edge - aa, edge + aa, mask); - float outlineField = smoothstep(edge - outlineAmount - aa, edge - outlineAmount + aa, mask); - float outline = saturate(outlineField - fill); - float textAlpha = max(fill * fillColor.a, outline * outlineColor.a); + float shadowRadius = min((outlineWidth + softness) * 0.025, 0.018); + float shadow = 0.0; + if (shadowRadius > 0.0001) + { + shadow = max(shadow, sampleTitleText(textUv + float2(shadowRadius, shadowRadius))); + shadow = max(shadow, sampleTitleText(textUv + float2(-shadowRadius, shadowRadius))); + shadow = max(shadow, sampleTitleText(textUv + float2(shadowRadius, -shadowRadius))); + shadow = max(shadow, sampleTitleText(textUv + float2(-shadowRadius, -shadowRadius))); + } + shadow = smoothstep(edge - aa, edge + aa, shadow) * (0.35 + softness); + float outlineAlpha = saturate(shadow * (1.0 - fill)) * outlineColor.a; + float fillAlpha = fill * fillColor.a; + float textAlpha = max(fillAlpha, outlineAlpha); if (textAlpha <= 0.0001) return context.sourceColor; float4 base = context.sourceColor; - float4 outlineLayer = float4(outlineColor.rgb * outlineColor.a, outline * outlineColor.a); - float4 fillLayer = float4(fillColor.rgb * fillColor.a, fill * fillColor.a); + float4 outlineLayer = float4(outlineColor.rgb * outlineAlpha, outlineAlpha); + float4 fillLayer = float4(fillColor.rgb * fillAlpha, fillAlpha); return saturate(compositeOver(compositeOver(base, outlineLayer), fillLayer)); } -- 2.49.1