Compare commits
4 Commits
c5f0a9df0e
...
c35ca8d61c
| Author | SHA1 | Date | |
|---|---|---|---|
| c35ca8d61c | |||
| be9f3b4e8b | |||
| af448c338c | |||
| 283f38dddb |
@@ -318,3 +318,6 @@ If `SLANG_ROOT` or `MSDF_ATLAS_GEN_ROOT` is not set, the workflow falls back to
|
|||||||
- Anotate included shaders
|
- Anotate included shaders
|
||||||
- allow 3 vector exposed controls
|
- allow 3 vector exposed controls
|
||||||
- add nearest sampling to the extra shader pass
|
- add nearest sampling to the extra shader pass
|
||||||
|
- add spout input/output (https://github.com/leadedge/Spout2)
|
||||||
|
- Add Aja input and output (Assuming i can get a hold of an aja card)
|
||||||
|
- Add bluefish input and output (Assuming again card acess)
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
"oscPort": 9000,
|
"oscPort": 9000,
|
||||||
"oscSmoothing": 0.18,
|
"oscSmoothing": 0.18,
|
||||||
"input": {
|
"input": {
|
||||||
"backend": "none",
|
"backend": "ndi",
|
||||||
"device": "AIDENLAPTOP (NVIDIA GeForce RTX 4090 Laptop GPU 1)",
|
"device": "AIDENLAPTOP (Test Pattern)",
|
||||||
"resolution": "1080p",
|
"resolution": "1080p",
|
||||||
"frameRate": "59.94"
|
"frameRate": "59.94"
|
||||||
},
|
},
|
||||||
"output": {
|
"output": {
|
||||||
"backend": "ndi",
|
"backend": "decklink",
|
||||||
"device": "shader toys",
|
"device": "default",
|
||||||
"resolution": "1080p",
|
"resolution": "1080p",
|
||||||
"frameRate": "59.94",
|
"frameRate": "59.94",
|
||||||
"keying": {
|
"keying": {
|
||||||
|
|||||||
@@ -1079,6 +1079,9 @@ components:
|
|||||||
font:
|
font:
|
||||||
type: string
|
type: string
|
||||||
description: Font asset id used by text parameters, when declared.
|
description: Font asset id used by text parameters, when declared.
|
||||||
|
fontParameter:
|
||||||
|
type: string
|
||||||
|
description: Enum parameter id used to select a text parameter font at runtime.
|
||||||
value:
|
value:
|
||||||
description: Current parameter value.
|
description: Current parameter value.
|
||||||
oneOf:
|
oneOf:
|
||||||
|
|||||||
@@ -592,6 +592,40 @@ 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.
|
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.
|
||||||
|
|
||||||
|
Text parameters can also choose their font from an enum parameter by setting `fontParameter` to that enum parameter's `id`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fonts": [
|
||||||
|
{ "id": "inter", "path": "fonts/Inter-Regular.ttf" },
|
||||||
|
{ "id": "mono", "path": "fonts/Mono-Regular.ttf" }
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "font",
|
||||||
|
"label": "Font",
|
||||||
|
"type": "enum",
|
||||||
|
"default": "inter",
|
||||||
|
"options": [
|
||||||
|
{ "value": "inter", "label": "Inter" },
|
||||||
|
{ "value": "mono", "label": "Mono" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "titleText",
|
||||||
|
"label": "Title",
|
||||||
|
"type": "text",
|
||||||
|
"default": "LIVE",
|
||||||
|
"font": "inter",
|
||||||
|
"fontParameter": "font",
|
||||||
|
"maxLength": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every option `value` in the font selector enum must match a declared font asset `id`. The `font` field remains useful as the default/fallback font for the text parameter, while `fontParameter` lets operators switch atlases at runtime without adding shader-specific code.
|
||||||
|
|
||||||
Trigger example:
|
Trigger example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -619,6 +653,7 @@ Parameter validation:
|
|||||||
- `color` must have exactly 4 numbers.
|
- `color` must have exactly 4 numbers.
|
||||||
- Enum defaults must match one of the declared option values.
|
- 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`.
|
- Text defaults must be strings. Non-printable characters are dropped and values are clamped to `maxLength`.
|
||||||
|
- Text `fontParameter` values must reference an enum parameter whose option values are declared font asset IDs.
|
||||||
- Trigger values are incremented by the host when triggered. The shader sees the trigger count and last trigger time.
|
- Trigger values are incremented by the host when triggered. The shader sees the trigger count and last trigger time.
|
||||||
- Non-finite numeric values are rejected.
|
- Non-finite numeric values are rejected.
|
||||||
|
|
||||||
@@ -800,6 +835,7 @@ For multipass shaders, these files reflect the most recently compiled pass. If a
|
|||||||
- Remember enum globals are integer indexes, not strings.
|
- Remember enum globals are integer indexes, not strings.
|
||||||
- Declare every texture in `shader.json`; undeclared texture samplers will not be bound.
|
- 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.
|
- Declare packaged fonts in `shader.json` when text parameters should use a specific font.
|
||||||
|
- For selectable fonts, use a text parameter `fontParameter` that points at an enum whose option values are font IDs.
|
||||||
- Keep temporal history requests modest. They consume texture units and memory and are capped by runtime config.
|
- 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 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.
|
- If a Slang name collides with a generated global, rename your parameter or local symbol.
|
||||||
@@ -815,5 +851,6 @@ Before committing a new shader package:
|
|||||||
- Texture files referenced by `textures` exist.
|
- Texture files referenced by `textures` exist.
|
||||||
- Font files referenced by `fonts` exist.
|
- Font files referenced by `fonts` exist.
|
||||||
- Enum defaults are present in their `options`.
|
- Enum defaults are present in their `options`.
|
||||||
|
- Text `fontParameter` selectors reference valid font assets through their enum options.
|
||||||
- Temporal shaders handle short or empty history gracefully.
|
- Temporal shaders handle short or empty history gracefully.
|
||||||
- The app can reload and compile the shader without errors.
|
- The app can reload and compile the shader without errors.
|
||||||
|
|||||||
BIN
shaders/text-overlay/fonts/AnalogMono.ttf
Normal file
BIN
shaders/text-overlay/fonts/AnalogMono.ttf
Normal file
Binary file not shown.
60
shaders/text-overlay/fonts/LICENSE.txt
Normal file
60
shaders/text-overlay/fonts/LICENSE.txt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
Analog Mono Plus Pixel Font - License Agreement
|
||||||
|
Copyright © Andrew Gleeson, 2026
|
||||||
|
heygleeson@gmail.com
|
||||||
|
|
||||||
|
1. GRANT OF LICENSE
|
||||||
|
|
||||||
|
This Agreement is a license, not an agreement of sale. Licensee shall
|
||||||
|
not acquire any copyright ownership or equivalent rights to any of the
|
||||||
|
Licensed Content. Seller and the Licensed Content sources retain all
|
||||||
|
right, title and interest in and to all of the copyrights, trademarks,
|
||||||
|
and all other proprietary rights in the Licensed Content. All rights
|
||||||
|
in and to Licensed Content not expressly granted in this agreement are
|
||||||
|
retained by Seller or its suppliers.
|
||||||
|
|
||||||
|
Licensee is permitted to use the Licensed Content in unlimited
|
||||||
|
commercial projects. A commercial project is one defined as a Work for
|
||||||
|
Distribution launched with the capability to generate revenue, or
|
||||||
|
intention to generate revenue through the sale of, licensing of, or
|
||||||
|
otherwise intend to generate revenue directly from the Work for
|
||||||
|
Distribution.
|
||||||
|
|
||||||
|
2. RESTRICTION ON USE
|
||||||
|
|
||||||
|
Licensed Content may not be used contrary to any restriction on use
|
||||||
|
indicated herein.
|
||||||
|
|
||||||
|
Licensed Content may not be resold, sublicensed, assigned, transferred
|
||||||
|
or otherwise made available to third parties except as incorporated
|
||||||
|
into Works for Distribution.
|
||||||
|
|
||||||
|
Licensed Content may not be distributed to third parties as standalone
|
||||||
|
files or in a way that unreasonably permits the recipient to extract
|
||||||
|
the Licensed Content for use separately and apart from the Work for
|
||||||
|
Distribution.
|
||||||
|
|
||||||
|
Licensee may not distribute the Licensed Content in any library or
|
||||||
|
reusable template, including but not limited to game templates, website
|
||||||
|
templates intended to allow reproduction by third parties on electronic
|
||||||
|
or printed products.
|
||||||
|
|
||||||
|
Licensee may not distribute Licensed Content in a manner meant to enable
|
||||||
|
third parties to create derivative works incorporating Licensed Content.
|
||||||
|
|
||||||
|
Licensee may not superficially modify the Licensed Content and sell it
|
||||||
|
to others for consumption, reproduction or re-sale.
|
||||||
|
|
||||||
|
Licensee shall not use the Licensed Content in a manner that violates
|
||||||
|
the law of any applicable jurisdiction.
|
||||||
|
|
||||||
|
Licensee shall not claim copyright or attribution of Licensed Content.
|
||||||
|
|
||||||
|
3. TERM AND TERMINATION
|
||||||
|
|
||||||
|
The license contained in this Agreement terminates automatically without
|
||||||
|
notice from Seller if Licensee fails to comply with any provision of
|
||||||
|
this Agreement. Upon termination, Licensee must with immediate effect
|
||||||
|
stop using the Licensed Content, destroy, delete and remove the Licensed
|
||||||
|
Content from Licensee’s premises, computer systems and storage. Licensee
|
||||||
|
must also make all reasonable efforts to ensure that copies of the
|
||||||
|
licensed content are removed from any locations it has been distributed to.
|
||||||
@@ -8,15 +8,37 @@
|
|||||||
{
|
{
|
||||||
"id": "roboto",
|
"id": "roboto",
|
||||||
"path": "fonts/Roboto-Regular.ttf"
|
"path": "fonts/Roboto-Regular.ttf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "analogMono",
|
||||||
|
"path": "fonts/AnalogMono.ttf"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "font",
|
||||||
|
"label": "Font",
|
||||||
|
"type": "enum",
|
||||||
|
"default": "roboto",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "roboto",
|
||||||
|
"label": "Roboto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "analogMono",
|
||||||
|
"label": "Analog Mono"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Font atlas used by the text overlay."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "titleText",
|
"id": "titleText",
|
||||||
"label": "Text",
|
"label": "Text",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"default": "VIDEO SHADER",
|
"default": "VIDEO SHADER",
|
||||||
"font": "roboto",
|
"font": "roboto",
|
||||||
|
"fontParameter": "font",
|
||||||
"maxLength": 64,
|
"maxLength": 64,
|
||||||
"description": "Text string rendered into the SDF text texture."
|
"description": "Text string rendered into the SDF text texture."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ float4 shadeVideo(ShaderContext context)
|
|||||||
float2 pixelTextUv = (1.0 / resolution) / safeTextSize;
|
float2 pixelTextUv = (1.0 / resolution) / safeTextSize;
|
||||||
float2 sampleOffset = pixelTextUv * 0.38;
|
float2 sampleOffset = pixelTextUv * 0.38;
|
||||||
float msdfDistance = sampleTitleTextMsdf(textUv);
|
float msdfDistance = sampleTitleTextMsdf(textUv);
|
||||||
float fill = (
|
float msdfFill = (
|
||||||
coverage(msdfDistance, edge, aa) * 2.0 +
|
coverage(msdfDistance, edge, aa) * 2.0 +
|
||||||
coverage(sampleTitleTextMsdf(textUv + float2(sampleOffset.x, sampleOffset.y)), edge, aa) +
|
coverage(sampleTitleTextMsdf(textUv + float2(sampleOffset.x, sampleOffset.y)), edge, aa) +
|
||||||
coverage(sampleTitleTextMsdf(textUv + float2(-sampleOffset.x, sampleOffset.y)), edge, aa) +
|
coverage(sampleTitleTextMsdf(textUv + float2(-sampleOffset.x, sampleOffset.y)), edge, aa) +
|
||||||
coverage(sampleTitleTextMsdf(textUv + float2(sampleOffset.x, -sampleOffset.y)), edge, aa) +
|
coverage(sampleTitleTextMsdf(textUv + float2(sampleOffset.x, -sampleOffset.y)), edge, aa) +
|
||||||
coverage(sampleTitleTextMsdf(textUv + float2(-sampleOffset.x, -sampleOffset.y)), edge, aa)) / 6.0;
|
coverage(sampleTitleTextMsdf(textUv + float2(-sampleOffset.x, -sampleOffset.y)), edge, aa)) / 6.0;
|
||||||
|
float sdfFill = coverage(distance, edge, aa);
|
||||||
|
float fill = min(msdfFill, sdfFill);
|
||||||
float outlineEdge = edge - min(outlineWidth * 0.7, 0.48);
|
float outlineEdge = edge - min(outlineWidth * 0.7, 0.48);
|
||||||
float outline = coverage(distance, outlineEdge, aa);
|
float outline = coverage(distance, outlineEdge, aa);
|
||||||
float outlineAlpha = saturate(outline - fill) * outlineColor.a;
|
float outlineAlpha = saturate(outline - fill) * outlineColor.a;
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
|
|||||||
writer.KeyUInt("maxLength", parameter.maxLength);
|
writer.KeyUInt("maxLength", parameter.maxLength);
|
||||||
if (!parameter.fontId.empty())
|
if (!parameter.fontId.empty())
|
||||||
writer.KeyString("font", parameter.fontId);
|
writer.KeyString("font", parameter.fontId);
|
||||||
|
if (!parameter.fontParameterId.empty())
|
||||||
|
writer.KeyString("fontParameter", parameter.fontParameterId);
|
||||||
}
|
}
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,8 +103,14 @@ bool RuntimeTextTextureCache::EnsureTextTexture(TextTexture& texture)
|
|||||||
const RuntimePreparedTextTexture* prepared = FindPreparedTexture(texture.parameterId);
|
const RuntimePreparedTextTexture* prepared = FindPreparedTexture(texture.parameterId);
|
||||||
if (!prepared || !prepared->rgbaPixels || prepared->rgbaPixels->empty() || prepared->width == 0 || prepared->height == 0)
|
if (!prepared || !prepared->rgbaPixels || prepared->rgbaPixels->empty() || prepared->width == 0 || prepared->height == 0)
|
||||||
return false;
|
return false;
|
||||||
if (texture.texture != 0 && texture.cachedText == prepared->textValue && texture.width == prepared->width && texture.height == prepared->height)
|
if (texture.texture != 0 &&
|
||||||
|
texture.cachedText == prepared->textValue &&
|
||||||
|
texture.cachedPixels == prepared->rgbaPixels &&
|
||||||
|
texture.width == prepared->width &&
|
||||||
|
texture.height == prepared->height)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (texture.texture == 0)
|
if (texture.texture == 0)
|
||||||
glGenTextures(1, &texture.texture);
|
glGenTextures(1, &texture.texture);
|
||||||
@@ -130,6 +136,7 @@ bool RuntimeTextTextureCache::EnsureTextTexture(TextTexture& texture)
|
|||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
texture.cachedText = prepared->textValue;
|
texture.cachedText = prepared->textValue;
|
||||||
|
texture.cachedPixels = prepared->rgbaPixels;
|
||||||
texture.width = prepared->width;
|
texture.width = prepared->width;
|
||||||
texture.height = prepared->height;
|
texture.height = prepared->height;
|
||||||
texture.liveWidth = prepared->liveWidth;
|
texture.liveWidth = prepared->liveWidth;
|
||||||
@@ -154,4 +161,5 @@ void RuntimeTextTextureCache::DestroyTexture(TextTexture& texture)
|
|||||||
texture.width = 0;
|
texture.width = 0;
|
||||||
texture.height = 0;
|
texture.height = 0;
|
||||||
texture.cachedText.clear();
|
texture.cachedText.clear();
|
||||||
|
texture.cachedPixels.reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "RuntimeShaderArtifact.h"
|
#include "RuntimeShaderArtifact.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ private:
|
|||||||
{
|
{
|
||||||
std::string parameterId;
|
std::string parameterId;
|
||||||
std::string cachedText;
|
std::string cachedText;
|
||||||
|
std::shared_ptr<const std::vector<unsigned char>> cachedPixels;
|
||||||
GLuint texture = 0;
|
GLuint texture = 0;
|
||||||
unsigned width = 0;
|
unsigned width = 0;
|
||||||
unsigned height = 0;
|
unsigned height = 0;
|
||||||
|
|||||||
@@ -8,6 +8,29 @@
|
|||||||
|
|
||||||
namespace RenderCadenceCompositor
|
namespace RenderCadenceCompositor
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const ShaderParameterDefinition* FindParameterDefinition(const ShaderPackage& shaderPackage, const std::string& parameterId)
|
||||||
|
{
|
||||||
|
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
|
||||||
|
{
|
||||||
|
if (parameter.id == parameterId)
|
||||||
|
return ¶meter;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasFontAsset(const ShaderPackage& shaderPackage, const std::string& fontId)
|
||||||
|
{
|
||||||
|
for (const ShaderFontAsset& fontAsset : shaderPackage.fontAssets)
|
||||||
|
{
|
||||||
|
if (fontAsset.id == fontId)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
|
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
|
||||||
{
|
{
|
||||||
if (shaderPackage.passes.empty())
|
if (shaderPackage.passes.empty())
|
||||||
@@ -27,20 +50,24 @@ ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& s
|
|||||||
if (parameter.type != ShaderParameterType::Text)
|
if (parameter.type != ShaderParameterType::Text)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (parameter.fontId.empty())
|
if (parameter.fontId.empty() && parameter.fontParameterId.empty())
|
||||||
return { false, "Text parameter '" + parameter.id + "' must reference a declared font asset." };
|
return { false, "Text parameter '" + parameter.id + "' must reference a declared font asset." };
|
||||||
|
|
||||||
bool hasFontAsset = false;
|
if (!parameter.fontId.empty() && !HasFontAsset(shaderPackage, parameter.fontId))
|
||||||
for (const ShaderFontAsset& fontAsset : shaderPackage.fontAssets)
|
|
||||||
{
|
|
||||||
if (fontAsset.id == parameter.fontId)
|
|
||||||
{
|
|
||||||
hasFontAsset = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasFontAsset)
|
|
||||||
return { false, "Text parameter '" + parameter.id + "' references unknown font asset '" + parameter.fontId + "'." };
|
return { false, "Text parameter '" + parameter.id + "' references unknown font asset '" + parameter.fontId + "'." };
|
||||||
|
|
||||||
|
if (!parameter.fontParameterId.empty())
|
||||||
|
{
|
||||||
|
const ShaderParameterDefinition* fontParameter = FindParameterDefinition(shaderPackage, parameter.fontParameterId);
|
||||||
|
if (fontParameter == nullptr || fontParameter->type != ShaderParameterType::Enum)
|
||||||
|
return { false, "Text parameter '" + parameter.id + "' references unknown font enum parameter '" + parameter.fontParameterId + "'." };
|
||||||
|
|
||||||
|
for (const ShaderParameterOption& option : fontParameter->enumOptions)
|
||||||
|
{
|
||||||
|
if (!HasFontAsset(shaderPackage, option.value))
|
||||||
|
return { false, "Font enum parameter '" + fontParameter->id + "' references unknown font asset '" + option.value + "'." };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool writesLayerOutput = false;
|
bool writesLayerOutput = false;
|
||||||
@@ -102,6 +129,7 @@ std::string ShaderPackageFingerprint(const ShaderPackage& shaderPackage)
|
|||||||
{
|
{
|
||||||
source << "param:" << parameter.id << ":" << static_cast<int>(parameter.type) << ":"
|
source << "param:" << parameter.id << ":" << static_cast<int>(parameter.type) << ":"
|
||||||
<< parameter.label << ":" << parameter.description << ":" << parameter.fontId << ":"
|
<< parameter.label << ":" << parameter.description << ":" << parameter.fontId << ":"
|
||||||
|
<< parameter.fontParameterId << ":"
|
||||||
<< parameter.defaultTextValue << ":" << parameter.defaultBoolean << ":"
|
<< parameter.defaultTextValue << ":" << parameter.defaultBoolean << ":"
|
||||||
<< parameter.defaultEnumValue << ":" << parameter.maxLength << "\n";
|
<< parameter.defaultEnumValue << ":" << parameter.maxLength << "\n";
|
||||||
for (double value : parameter.defaultNumbers)
|
for (double value : parameter.defaultNumbers)
|
||||||
|
|||||||
@@ -178,7 +178,17 @@ bool RuntimeLayerModel::UpdateParameter(const std::string& layerId, const std::s
|
|||||||
if (layer->renderReady)
|
if (layer->renderReady)
|
||||||
{
|
{
|
||||||
layer->artifact.parameterValues = layer->parameterValues;
|
layer->artifact.parameterValues = layer->parameterValues;
|
||||||
if (definition->type == ShaderParameterType::Text && !PrepareRuntimeTextTextures(layer->artifact, error))
|
bool textTexturesDependOnParameter = false;
|
||||||
|
for (const ShaderParameterDefinition& textDefinition : layer->parameterDefinitions)
|
||||||
|
{
|
||||||
|
if (textDefinition.type == ShaderParameterType::Text &&
|
||||||
|
(textDefinition.id == parameterId || textDefinition.fontParameterId == parameterId))
|
||||||
|
{
|
||||||
|
textTexturesDependOnParameter = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (textTexturesDependOnParameter && !PrepareRuntimeTextTextures(layer->artifact, error))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
error.clear();
|
error.clear();
|
||||||
|
|||||||
@@ -27,12 +27,38 @@ const ShaderParameterValue* FindParameterValue(const RuntimeShaderArtifact& arti
|
|||||||
return valueIt == artifact.parameterValues.end() ? nullptr : &valueIt->second;
|
return valueIt == artifact.parameterValues.end() ? nullptr : &valueIt->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ShaderParameterDefinition* FindParameterDefinition(const RuntimeShaderArtifact& artifact, const std::string& parameterId)
|
||||||
|
{
|
||||||
|
for (const ShaderParameterDefinition& definition : artifact.parameterDefinitions)
|
||||||
|
{
|
||||||
|
if (definition.id == parameterId)
|
||||||
|
return &definition;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::string TextValueForDefinition(const RuntimeShaderArtifact& artifact, const ShaderParameterDefinition& definition)
|
std::string TextValueForDefinition(const RuntimeShaderArtifact& artifact, const ShaderParameterDefinition& definition)
|
||||||
{
|
{
|
||||||
const ShaderParameterValue* value = FindParameterValue(artifact, definition.id);
|
const ShaderParameterValue* value = FindParameterValue(artifact, definition.id);
|
||||||
return value ? value->textValue : definition.defaultTextValue;
|
return value ? value->textValue : definition.defaultTextValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string FontIdForTextDefinition(const RuntimeShaderArtifact& artifact, const ShaderParameterDefinition& definition)
|
||||||
|
{
|
||||||
|
if (definition.fontParameterId.empty())
|
||||||
|
return definition.fontId;
|
||||||
|
|
||||||
|
const ShaderParameterValue* fontValue = FindParameterValue(artifact, definition.fontParameterId);
|
||||||
|
if (fontValue != nullptr && !fontValue->enumValue.empty())
|
||||||
|
return fontValue->enumValue;
|
||||||
|
|
||||||
|
const ShaderParameterDefinition* fontDefinition = FindParameterDefinition(artifact, definition.fontParameterId);
|
||||||
|
if (fontDefinition != nullptr && !fontDefinition->defaultEnumValue.empty())
|
||||||
|
return fontDefinition->defaultEnumValue;
|
||||||
|
|
||||||
|
return definition.fontId;
|
||||||
|
}
|
||||||
|
|
||||||
void SampleAtlasPixel(const FontAtlasBuildOutput& atlas, double x, double y, unsigned char* rgba)
|
void SampleAtlasPixel(const FontAtlasBuildOutput& atlas, double x, double y, unsigned char* rgba)
|
||||||
{
|
{
|
||||||
const double clampedX = (std::max)(0.0, (std::min)(static_cast<double>(atlas.width) - 1.0, x));
|
const double clampedX = (std::max)(0.0, (std::min)(static_cast<double>(atlas.width) - 1.0, x));
|
||||||
@@ -57,6 +83,13 @@ void SampleAtlasPixel(const FontAtlasBuildOutput& atlas, double x, double y, uns
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double GlyphAtlasCoordinate(double minBound, double maxBound, double uv)
|
||||||
|
{
|
||||||
|
if (maxBound - minBound <= 1.0)
|
||||||
|
return (minBound + maxBound) * 0.5;
|
||||||
|
return minBound + 0.5 + uv * ((maxBound - 0.5) - (minBound + 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> ComposeTextTexture(
|
std::vector<unsigned char> ComposeTextTexture(
|
||||||
const FontAtlasBuildOutput& atlas,
|
const FontAtlasBuildOutput& atlas,
|
||||||
const ShaderParameterDefinition& definition,
|
const ShaderParameterDefinition& definition,
|
||||||
@@ -109,8 +142,8 @@ std::vector<unsigned char> ComposeTextTexture(
|
|||||||
if (x < 0 || x >= static_cast<int>(width))
|
if (x < 0 || x >= static_cast<int>(width))
|
||||||
continue;
|
continue;
|
||||||
const double u = (static_cast<double>(x) + 0.5 - destLeft) / destWidth;
|
const double u = (static_cast<double>(x) + 0.5 - destLeft) / destWidth;
|
||||||
const double atlasX = glyph.atlasLeft + u * (glyph.atlasRight - glyph.atlasLeft);
|
const double atlasX = GlyphAtlasCoordinate(glyph.atlasLeft, glyph.atlasRight, u);
|
||||||
const double atlasY = glyph.atlasTop + v * (glyph.atlasBottom - glyph.atlasTop);
|
const double atlasY = GlyphAtlasCoordinate(glyph.atlasTop, glyph.atlasBottom, v);
|
||||||
unsigned char sample[4] = {};
|
unsigned char sample[4] = {};
|
||||||
SampleAtlasPixel(atlas, atlasX, atlasY, sample);
|
SampleAtlasPixel(atlas, atlasX, atlasY, sample);
|
||||||
unsigned char* destination = texturePixels.data() + (static_cast<std::size_t>(y) * width + static_cast<std::size_t>(x)) * 4u;
|
unsigned char* destination = texturePixels.data() + (static_cast<std::size_t>(y) * width + static_cast<std::size_t>(x)) * 4u;
|
||||||
@@ -145,7 +178,8 @@ bool PrepareRuntimeTextTextures(RuntimeShaderArtifact& artifact, std::string& er
|
|||||||
if (definition.type != ShaderParameterType::Text)
|
if (definition.type != ShaderParameterType::Text)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const FontAtlasBuildOutput* atlas = FindAtlas(artifact, definition.fontId);
|
const std::string fontId = FontIdForTextDefinition(artifact, definition);
|
||||||
|
const FontAtlasBuildOutput* atlas = FindAtlas(artifact, fontId);
|
||||||
if (atlas == nullptr)
|
if (atlas == nullptr)
|
||||||
{
|
{
|
||||||
error = "No prepared font atlas is available for text parameter '" + definition.id + "'.";
|
error = "No prepared font atlas is available for text parameter '" + definition.id + "'.";
|
||||||
@@ -153,7 +187,7 @@ bool PrepareRuntimeTextTextures(RuntimeShaderArtifact& artifact, std::string& er
|
|||||||
}
|
}
|
||||||
if (atlas->width == 0 || atlas->height == 0 || atlas->rgbaPixels.empty() || atlas->glyphsByCodepoint.empty())
|
if (atlas->width == 0 || atlas->height == 0 || atlas->rgbaPixels.empty() || atlas->glyphsByCodepoint.empty())
|
||||||
{
|
{
|
||||||
error = "Prepared font atlas data is empty for font '" + definition.fontId + "'.";
|
error = "Prepared font atlas data is empty for font '" + fontId + "'.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,17 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef
|
|||||||
if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
|
if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (const JsonValue* fontParameterValue = parameterJson.find("fontParameter"))
|
||||||
|
{
|
||||||
|
if (!fontParameterValue->isString())
|
||||||
|
{
|
||||||
|
error = "Text parameter 'fontParameter' must be a string for: " + definition.id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
definition.fontParameterId = fontParameterValue->asString();
|
||||||
|
if (!definition.fontParameterId.empty() && !ValidateShaderIdentifier(definition.fontParameterId, "parameters[].fontParameter", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
|
if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
|
||||||
{
|
{
|
||||||
if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
|
if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ struct ShaderParameterDefinition
|
|||||||
std::string defaultEnumValue;
|
std::string defaultEnumValue;
|
||||||
std::string defaultTextValue;
|
std::string defaultTextValue;
|
||||||
std::string fontId;
|
std::string fontId;
|
||||||
|
std::string fontParameterId;
|
||||||
unsigned maxLength = 64;
|
unsigned maxLength = 64;
|
||||||
std::vector<ShaderParameterOption> enumOptions;
|
std::vector<ShaderParameterOption> enumOptions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -82,13 +82,13 @@ void TestBuildsTextOverlayFontAtlas()
|
|||||||
Expect(false, ("text overlay font atlas builds: " + error).c_str());
|
Expect(false, ("text overlay font atlas builds: " + error).c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Expect(outputs.size() == 1, "one font atlas output is produced");
|
Expect(outputs.size() == shaderPackage.fontAssets.size(), "one font atlas output is produced for each declared font");
|
||||||
if (!outputs.empty())
|
for (const RenderCadenceCompositor::FontAtlasBuildOutput& output : outputs)
|
||||||
{
|
{
|
||||||
Expect(std::filesystem::exists(outputs[0].imagePath), "font atlas image exists");
|
Expect(std::filesystem::exists(output.imagePath), "font atlas image exists");
|
||||||
Expect(std::filesystem::exists(outputs[0].jsonPath), "font atlas json exists");
|
Expect(std::filesystem::exists(output.jsonPath), "font atlas json exists");
|
||||||
Expect(std::filesystem::file_size(outputs[0].imagePath) > 0, "font atlas image is not empty");
|
Expect(std::filesystem::file_size(output.imagePath) > 0, "font atlas image is not empty");
|
||||||
Expect(std::filesystem::file_size(outputs[0].jsonPath) > 0, "font atlas json is not empty");
|
Expect(std::filesystem::file_size(output.jsonPath) > 0, "font atlas json is not empty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,17 +62,20 @@ std::string AllParametersShaderManifest()
|
|||||||
"description": "All parameter restore test shader",
|
"description": "All parameter restore test shader",
|
||||||
"category": "Tests",
|
"category": "Tests",
|
||||||
"entryPoint": "shadeVideo",
|
"entryPoint": "shadeVideo",
|
||||||
"fonts": [{ "id": "inter", "path": "Inter.ttf" }],
|
"fonts": [
|
||||||
|
{ "id": "inter", "path": "Inter.ttf" },
|
||||||
|
{ "id": "mono", "path": "Mono.ttf" }
|
||||||
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{ "id": "gain", "label": "Gain", "type": "float", "default": 0.5, "min": 0.0, "max": 1.0 },
|
{ "id": "gain", "label": "Gain", "type": "float", "default": 0.5, "min": 0.0, "max": 1.0 },
|
||||||
{ "id": "offset", "label": "Offset", "type": "vec2", "default": [0.0, 0.0], "min": [-1.0, -1.0], "max": [1.0, 1.0] },
|
{ "id": "offset", "label": "Offset", "type": "vec2", "default": [0.0, 0.0], "min": [-1.0, -1.0], "max": [1.0, 1.0] },
|
||||||
{ "id": "tint", "label": "Tint", "type": "color", "default": [1.0, 1.0, 1.0, 1.0], "min": [0.0, 0.0, 0.0, 0.0], "max": [1.0, 1.0, 1.0, 1.0] },
|
{ "id": "tint", "label": "Tint", "type": "color", "default": [1.0, 1.0, 1.0, 1.0], "min": [0.0, 0.0, 0.0, 0.0], "max": [1.0, 1.0, 1.0, 1.0] },
|
||||||
{ "id": "enabled", "label": "Enabled", "type": "bool", "default": true },
|
{ "id": "enabled", "label": "Enabled", "type": "bool", "default": true },
|
||||||
{ "id": "mode", "label": "Mode", "type": "enum", "default": "soft", "options": [
|
{ "id": "mode", "label": "Mode", "type": "enum", "default": "inter", "options": [
|
||||||
{ "value": "soft", "label": "Soft" },
|
{ "value": "inter", "label": "Inter" },
|
||||||
{ "value": "hard", "label": "Hard" }
|
{ "value": "mono", "label": "Mono" }
|
||||||
] },
|
] },
|
||||||
{ "id": "titleText", "label": "Title", "type": "text", "default": "DEFAULT", "font": "inter", "maxLength": 8 },
|
{ "id": "titleText", "label": "Title", "type": "text", "default": "DEFAULT", "font": "inter", "fontParameter": "mode", "maxLength": 8 },
|
||||||
{ "id": "drop", "label": "Drop", "type": "trigger" }
|
{ "id": "drop", "label": "Drop", "type": "trigger" }
|
||||||
]
|
]
|
||||||
})";
|
})";
|
||||||
@@ -94,10 +97,10 @@ RenderCadenceCompositor::SupportedShaderCatalog MakeCatalog(std::filesystem::pat
|
|||||||
return LoadCatalog(root);
|
return LoadCatalog(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderCadenceCompositor::FontAtlasBuildOutput MakeFakeFontAtlas()
|
RenderCadenceCompositor::FontAtlasBuildOutput MakeFakeFontAtlas(const std::string& fontId = "inter")
|
||||||
{
|
{
|
||||||
RenderCadenceCompositor::FontAtlasBuildOutput atlas;
|
RenderCadenceCompositor::FontAtlasBuildOutput atlas;
|
||||||
atlas.fontId = "inter";
|
atlas.fontId = fontId;
|
||||||
atlas.width = 2;
|
atlas.width = 2;
|
||||||
atlas.height = 2;
|
atlas.height = 2;
|
||||||
atlas.ascender = -0.8;
|
atlas.ascender = -0.8;
|
||||||
@@ -226,6 +229,7 @@ void TestInitializeFromRuntimeStateRestoresLayerStack()
|
|||||||
WriteFile(root / "solid" / "shader.json", SolidShaderManifest(0.5, false));
|
WriteFile(root / "solid" / "shader.json", SolidShaderManifest(0.5, false));
|
||||||
WriteFile(root / "all-params" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
WriteFile(root / "all-params" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
||||||
WriteFile(root / "all-params" / "Inter.ttf", "not a real font, but enough for restore catalog support checks");
|
WriteFile(root / "all-params" / "Inter.ttf", "not a real font, but enough for restore catalog support checks");
|
||||||
|
WriteFile(root / "all-params" / "Mono.ttf", "not a real font, but enough for restore catalog support checks");
|
||||||
WriteFile(root / "all-params" / "shader.json", AllParametersShaderManifest());
|
WriteFile(root / "all-params" / "shader.json", AllParametersShaderManifest());
|
||||||
RenderCadenceCompositor::SupportedShaderCatalog catalog = LoadCatalog(root);
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = LoadCatalog(root);
|
||||||
|
|
||||||
@@ -242,7 +246,7 @@ void TestInitializeFromRuntimeStateRestoresLayerStack()
|
|||||||
"offset": [0.25, -0.5],
|
"offset": [0.25, -0.5],
|
||||||
"tint": [0.1, 0.2, 0.3, 0.4],
|
"tint": [0.1, 0.2, 0.3, 0.4],
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"mode": "hard",
|
"mode": "mono",
|
||||||
"titleText": "RESTORED-TEXT",
|
"titleText": "RESTORED-TEXT",
|
||||||
"drop": 4
|
"drop": 4
|
||||||
}
|
}
|
||||||
@@ -275,7 +279,7 @@ void TestInitializeFromRuntimeStateRestoresLayerStack()
|
|||||||
Expect(snapshot.displayLayers[0].parameterValues.at("offset").numberValues == std::vector<double>({ 0.25, -0.5 }), "restore preserves vec2 parameter values");
|
Expect(snapshot.displayLayers[0].parameterValues.at("offset").numberValues == std::vector<double>({ 0.25, -0.5 }), "restore preserves vec2 parameter values");
|
||||||
Expect(snapshot.displayLayers[0].parameterValues.at("tint").numberValues == std::vector<double>({ 0.1, 0.2, 0.3, 0.4 }), "restore preserves color parameter values");
|
Expect(snapshot.displayLayers[0].parameterValues.at("tint").numberValues == std::vector<double>({ 0.1, 0.2, 0.3, 0.4 }), "restore preserves color parameter values");
|
||||||
Expect(!snapshot.displayLayers[0].parameterValues.at("enabled").booleanValue, "restore preserves boolean parameter values");
|
Expect(!snapshot.displayLayers[0].parameterValues.at("enabled").booleanValue, "restore preserves boolean parameter values");
|
||||||
Expect(snapshot.displayLayers[0].parameterValues.at("mode").enumValue == "hard", "restore preserves enum parameter values");
|
Expect(snapshot.displayLayers[0].parameterValues.at("mode").enumValue == "mono", "restore preserves enum parameter values");
|
||||||
Expect(snapshot.displayLayers[0].parameterValues.at("titleText").textValue == "RESTORED", "restore normalizes and preserves text parameter values");
|
Expect(snapshot.displayLayers[0].parameterValues.at("titleText").textValue == "RESTORED", "restore normalizes and preserves text parameter values");
|
||||||
Expect(snapshot.displayLayers[0].parameterValues.at("drop").numberValues.front() == 4.0, "restore preserves trigger counts");
|
Expect(snapshot.displayLayers[0].parameterValues.at("drop").numberValues.front() == 4.0, "restore preserves trigger counts");
|
||||||
Expect(snapshot.displayLayers[1].id == "layer-33", "restore preserves later supported layer order");
|
Expect(snapshot.displayLayers[1].id == "layer-33", "restore preserves later supported layer order");
|
||||||
@@ -488,6 +492,7 @@ void TestTextTexturesArePreparedInRuntimeModel()
|
|||||||
std::filesystem::path root = MakeTestRoot();
|
std::filesystem::path root = MakeTestRoot();
|
||||||
WriteFile(root / "all-params" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
WriteFile(root / "all-params" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
||||||
WriteFile(root / "all-params" / "Inter.ttf", "not a real font, but enough for catalog support checks");
|
WriteFile(root / "all-params" / "Inter.ttf", "not a real font, but enough for catalog support checks");
|
||||||
|
WriteFile(root / "all-params" / "Mono.ttf", "not a real font, but enough for catalog support checks");
|
||||||
WriteFile(root / "all-params" / "shader.json", AllParametersShaderManifest());
|
WriteFile(root / "all-params" / "shader.json", AllParametersShaderManifest());
|
||||||
RenderCadenceCompositor::SupportedShaderCatalog catalog = LoadCatalog(root);
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = LoadCatalog(root);
|
||||||
|
|
||||||
@@ -503,6 +508,7 @@ void TestTextTexturesArePreparedInRuntimeModel()
|
|||||||
artifact.fragmentShaderSource = "void main(){}";
|
artifact.fragmentShaderSource = "void main(){}";
|
||||||
artifact.parameterDefinitions = snapshot.displayLayers[0].parameterDefinitions;
|
artifact.parameterDefinitions = snapshot.displayLayers[0].parameterDefinitions;
|
||||||
artifact.fontAtlases.push_back(MakeFakeFontAtlas());
|
artifact.fontAtlases.push_back(MakeFakeFontAtlas());
|
||||||
|
artifact.fontAtlases.push_back(MakeFakeFontAtlas("mono"));
|
||||||
artifact.message = "build ready";
|
artifact.message = "build ready";
|
||||||
Expect(model.MarkBuildReady(artifact, error), error.empty() ? "ready text artifact prepares textures" : error);
|
Expect(model.MarkBuildReady(artifact, error), error.empty() ? "ready text artifact prepares textures" : error);
|
||||||
|
|
||||||
@@ -520,6 +526,12 @@ void TestTextTexturesArePreparedInRuntimeModel()
|
|||||||
Expect(preparedUpdated.textValue == "AB", "updated text is prepared before render snapshot");
|
Expect(preparedUpdated.textValue == "AB", "updated text is prepared before render snapshot");
|
||||||
Expect(preparedUpdated.rgbaPixels && preparedUpdated.rgbaPixels != preparedDefault.rgbaPixels, "updated text receives a new prepared pixel payload");
|
Expect(preparedUpdated.rgbaPixels && preparedUpdated.rgbaPixels != preparedDefault.rgbaPixels, "updated text receives a new prepared pixel payload");
|
||||||
|
|
||||||
|
Expect(model.UpdateParameter(model.FirstLayerId(), "mode", JsonValue("mono"), error), error.empty() ? "font selector update prepares texture" : error);
|
||||||
|
snapshot = model.Snapshot();
|
||||||
|
const RuntimePreparedTextTexture preparedWithNewFont = snapshot.renderLayers[0].artifact.preparedTextTextures[0];
|
||||||
|
Expect(preparedWithNewFont.textValue == "AB", "font selector update preserves current text");
|
||||||
|
Expect(preparedWithNewFont.rgbaPixels && preparedWithNewFont.rgbaPixels != preparedUpdated.rgbaPixels, "font selector update receives a new prepared pixel payload");
|
||||||
|
|
||||||
std::filesystem::remove_all(root);
|
std::filesystem::remove_all(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,6 +174,65 @@ void SupportsTextParametersWithDeclaredFont()
|
|||||||
Expect(result.reason.empty(), "supported text parameters should not report a rejection reason");
|
Expect(result.reason.empty(), "supported text parameters should not report a rejection reason");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SupportsTextParametersWithFontSelector()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
ShaderFontAsset roboto;
|
||||||
|
roboto.id = "roboto";
|
||||||
|
shaderPackage.fontAssets.push_back(roboto);
|
||||||
|
ShaderFontAsset mono;
|
||||||
|
mono.id = "mono";
|
||||||
|
shaderPackage.fontAssets.push_back(mono);
|
||||||
|
|
||||||
|
ShaderParameterDefinition fontSelector;
|
||||||
|
fontSelector.id = "font";
|
||||||
|
fontSelector.type = ShaderParameterType::Enum;
|
||||||
|
fontSelector.defaultEnumValue = "roboto";
|
||||||
|
fontSelector.enumOptions = { { "roboto", "Roboto" }, { "mono", "Mono" } };
|
||||||
|
shaderPackage.parameters.push_back(fontSelector);
|
||||||
|
|
||||||
|
ShaderParameterDefinition parameter;
|
||||||
|
parameter.id = "caption";
|
||||||
|
parameter.type = ShaderParameterType::Text;
|
||||||
|
parameter.fontId = "roboto";
|
||||||
|
parameter.fontParameterId = "font";
|
||||||
|
shaderPackage.parameters.push_back(parameter);
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(result.supported, "text parameters with font selector enum should be supported");
|
||||||
|
Expect(result.reason.empty(), "supported font selector text parameters should not report a rejection reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RejectsTextParametersWithFontSelectorUnknownOption()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
ShaderFontAsset roboto;
|
||||||
|
roboto.id = "roboto";
|
||||||
|
shaderPackage.fontAssets.push_back(roboto);
|
||||||
|
|
||||||
|
ShaderParameterDefinition fontSelector;
|
||||||
|
fontSelector.id = "font";
|
||||||
|
fontSelector.type = ShaderParameterType::Enum;
|
||||||
|
fontSelector.defaultEnumValue = "roboto";
|
||||||
|
fontSelector.enumOptions = { { "roboto", "Roboto" }, { "missing", "Missing" } };
|
||||||
|
shaderPackage.parameters.push_back(fontSelector);
|
||||||
|
|
||||||
|
ShaderParameterDefinition parameter;
|
||||||
|
parameter.id = "caption";
|
||||||
|
parameter.type = ShaderParameterType::Text;
|
||||||
|
parameter.fontId = "roboto";
|
||||||
|
parameter.fontParameterId = "font";
|
||||||
|
shaderPackage.parameters.push_back(parameter);
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(!result.supported, "font selector enum options must reference declared font assets");
|
||||||
|
Expect(result.reason.find("unknown font asset") != std::string::npos, "font selector rejection mentions unknown font asset");
|
||||||
|
}
|
||||||
|
|
||||||
void BuildsDeclaredFontAtlasesDuringCatalogLoad()
|
void BuildsDeclaredFontAtlasesDuringCatalogLoad()
|
||||||
{
|
{
|
||||||
std::string msdfReason;
|
std::string msdfReason;
|
||||||
@@ -217,6 +276,8 @@ int main()
|
|||||||
RejectsTextureAssets();
|
RejectsTextureAssets();
|
||||||
RejectsTextParametersWithoutDeclaredFont();
|
RejectsTextParametersWithoutDeclaredFont();
|
||||||
SupportsTextParametersWithDeclaredFont();
|
SupportsTextParametersWithDeclaredFont();
|
||||||
|
SupportsTextParametersWithFontSelector();
|
||||||
|
RejectsTextParametersWithFontSelectorUnknownOption();
|
||||||
BuildsDeclaredFontAtlasesDuringCatalogLoad();
|
BuildsDeclaredFontAtlasesDuringCatalogLoad();
|
||||||
|
|
||||||
if (gFailures != 0)
|
if (gFailures != 0)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ void TestValidManifest()
|
|||||||
const std::filesystem::path root = MakeTestRoot();
|
const std::filesystem::path root = MakeTestRoot();
|
||||||
WriteFile(root / "look" / "mask.png", "not a real png, but enough for existence checks");
|
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");
|
WriteFile(root / "look" / "Inter.ttf", "not a real font, but enough for existence checks");
|
||||||
|
WriteFile(root / "look" / "Mono.ttf", "not a real font, but enough for existence checks");
|
||||||
WriteShaderPackage(root, "look", R"({
|
WriteShaderPackage(root, "look", R"({
|
||||||
"id": "look-01",
|
"id": "look-01",
|
||||||
"name": "Look 01",
|
"name": "Look 01",
|
||||||
@@ -56,15 +57,18 @@ void TestValidManifest()
|
|||||||
"category": "Tests",
|
"category": "Tests",
|
||||||
"entryPoint": "shadeVideo",
|
"entryPoint": "shadeVideo",
|
||||||
"textures": [{ "id": "maskTex", "path": "mask.png" }],
|
"textures": [{ "id": "maskTex", "path": "mask.png" }],
|
||||||
"fonts": [{ "id": "inter", "path": "Inter.ttf" }],
|
"fonts": [
|
||||||
|
{ "id": "inter", "path": "Inter.ttf" },
|
||||||
|
{ "id": "mono", "path": "Mono.ttf" }
|
||||||
|
],
|
||||||
"temporal": { "enabled": true, "historySource": "source", "historyLength": 8 },
|
"temporal": { "enabled": true, "historySource": "source", "historyLength": 8 },
|
||||||
"feedback": { "enabled": true },
|
"feedback": { "enabled": true },
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{ "id": "gain", "label": "Gain", "description": "Scales the output intensity.", "type": "float", "default": 0.5, "min": 0, "max": 1 },
|
{ "id": "gain", "label": "Gain", "description": "Scales the output intensity.", "type": "float", "default": 0.5, "min": 0, "max": 1 },
|
||||||
{ "id": "titleText", "label": "Title", "type": "text", "default": "LIVE", "font": "inter", "maxLength": 32 },
|
{ "id": "titleText", "label": "Title", "type": "text", "default": "LIVE", "font": "inter", "fontParameter": "mode", "maxLength": 32 },
|
||||||
{ "id": "mode", "label": "Mode", "type": "enum", "default": "soft", "options": [
|
{ "id": "mode", "label": "Mode", "type": "enum", "default": "inter", "options": [
|
||||||
{ "value": "soft", "label": "Soft" },
|
{ "value": "inter", "label": "Inter" },
|
||||||
{ "value": "hard", "label": "Hard" }
|
{ "value": "mono", "label": "Mono" }
|
||||||
] },
|
] },
|
||||||
{ "id": "flash", "label": "Flash", "type": "trigger" }
|
{ "id": "flash", "label": "Flash", "type": "trigger" }
|
||||||
]
|
]
|
||||||
@@ -76,12 +80,13 @@ void TestValidManifest()
|
|||||||
Expect(registry.ParseManifest(root / "look" / "shader.json", package, error), "valid manifest parses");
|
Expect(registry.ParseManifest(root / "look" / "shader.json", package, error), "valid manifest parses");
|
||||||
Expect(package.id == "look-01", "manifest id is preserved");
|
Expect(package.id == "look-01", "manifest id is preserved");
|
||||||
Expect(package.textureAssets.size() == 1 && package.textureAssets[0].id == "maskTex", "texture assets parse");
|
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.fontAssets.size() == 2 && package.fontAssets[0].id == "inter", "font assets parse");
|
||||||
Expect(package.temporal.enabled && package.temporal.effectiveHistoryLength == 4, "temporal history is capped");
|
Expect(package.temporal.enabled && package.temporal.effectiveHistoryLength == 4, "temporal history is capped");
|
||||||
Expect(package.feedback.enabled && package.feedback.writePassId == "main", "feedback defaults to the implicit main pass");
|
Expect(package.feedback.enabled && package.feedback.writePassId == "main", "feedback defaults to the implicit main pass");
|
||||||
Expect(package.parameters.size() == 4, "parameters parse");
|
Expect(package.parameters.size() == 4, "parameters parse");
|
||||||
Expect(package.parameters[0].description == "Scales the output intensity.", "parameter descriptions parse");
|
Expect(package.parameters[0].description == "Scales the output intensity.", "parameter descriptions parse");
|
||||||
Expect(package.parameters[1].type == ShaderParameterType::Text && package.parameters[1].defaultTextValue == "LIVE", "text parameter parses");
|
Expect(package.parameters[1].type == ShaderParameterType::Text && package.parameters[1].defaultTextValue == "LIVE", "text parameter parses");
|
||||||
|
Expect(package.parameters[1].fontParameterId == "mode", "text font selector parameter parses");
|
||||||
Expect(package.parameters[3].type == ShaderParameterType::Trigger, "trigger parameter parses");
|
Expect(package.parameters[3].type == ShaderParameterType::Trigger, "trigger parameter parses");
|
||||||
Expect(package.passes.size() == 1 && package.passes[0].id == "main", "legacy manifests get an implicit main pass");
|
Expect(package.passes.size() == 1 && package.passes[0].id == "main", "legacy manifests get an implicit main pass");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user