From 3e8b472f744af46b4bbcf39969b5f663a566d04b Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 5 May 2026 23:18:50 +1000 Subject: [PATCH] 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 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 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; }