Font working

This commit is contained in:
2026-05-05 23:47:08 +10:00
parent 3e8b472f74
commit 62c3ded1f8
9 changed files with 220 additions and 36 deletions

View File

@@ -236,7 +236,10 @@ Fonts
genlock genlock
Logs Logs
anamorphic desqueeze anamorphic desqueeze
solid color layer
refactor, cleanup of source files refactor, cleanup of source files
display URL (Maybe clicakable) for control in the windows app (Not on the output) display URL (Maybe clicakable) for control in the windows app (Not on the output)
Sound shader as seperate .slang in shader package? Sound shader as seperate .slang in shader package?
runtime date time runtime date time
Add a value control to the color wheels
![alt text](image.png)

View File

@@ -47,6 +47,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <cctype> #include <cctype>
#include <fstream>
#include <gdiplus.h> #include <gdiplus.h>
#include <wincodec.h> #include <wincodec.h>
#include <limits> #include <limits>
@@ -106,6 +107,31 @@ const char* kDecodeFragmentShaderSource =
" fragColor = rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n" " fragColor = rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n"
"}\n"; "}\n";
class GdiplusSession
{
public:
GdiplusSession()
{
Gdiplus::GdiplusStartupInput startupInput;
mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok;
}
~GdiplusSession()
{
if (mStarted)
Gdiplus::GdiplusShutdown(mToken);
}
GdiplusSession(const GdiplusSession&) = delete;
GdiplusSession& operator=(const GdiplusSession&) = delete;
bool started() const { return mStarted; }
private:
ULONG_PTR mToken = 0;
bool mStarted = false;
};
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage) void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{ {
if (!errorMessage || errorMessageSize <= 0) if (!errorMessage || errorMessageSize <= 0)
@@ -191,11 +217,87 @@ std::vector<unsigned char> BuildLocalSdf(const std::vector<unsigned char>& alpha
return sdf; return sdf;
} }
std::vector<unsigned char> FlipTextTextureForShaderUv(const std::vector<unsigned char>& pixels, unsigned width, unsigned height)
{
std::vector<unsigned char> flipped(pixels.size(), 0);
const std::size_t stride = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
const std::size_t srcOffset = static_cast<std::size_t>(y) * stride;
const std::size_t dstOffset = static_cast<std::size_t>(height - 1 - y) * stride;
std::memcpy(flipped.data() + dstOffset, pixels.data() + srcOffset, stride);
}
return flipped;
}
void WriteTextMaskDebugDump(const std::string& text, const std::vector<unsigned char>& alpha, const std::vector<unsigned char>& sdf, unsigned width, unsigned height)
{
try
{
std::filesystem::path debugDir = std::filesystem::current_path() / "runtime";
std::filesystem::create_directories(debugDir);
auto writePgm = [width, height](const std::filesystem::path& path, const std::vector<unsigned char>& gray, std::size_t stride)
{
std::ofstream out(path, std::ios::binary);
if (!out)
return;
out << "P5\n" << width << " " << height << "\n255\n";
for (unsigned y = 0; y < height; ++y)
{
for (unsigned x = 0; x < width; ++x)
out.put(static_cast<char>(gray[(static_cast<std::size_t>(y) * width + x) * stride]));
}
};
writePgm(debugDir / "text-mask-alpha-debug.pgm", alpha, 1);
writePgm(debugDir / "text-mask-sdf-debug.pgm", sdf, 4);
unsigned alphaMin = 255;
unsigned alphaMax = 0;
unsigned sdfMin = 255;
unsigned sdfMax = 0;
std::size_t alphaLit = 0;
std::size_t sdfLit = 0;
for (unsigned char value : alpha)
{
alphaMin = std::min<unsigned>(alphaMin, value);
alphaMax = std::max<unsigned>(alphaMax, value);
if (value > 0)
++alphaLit;
}
for (std::size_t index = 0; index < sdf.size(); index += 4)
{
const unsigned char value = sdf[index];
sdfMin = std::min<unsigned>(sdfMin, value);
sdfMax = std::max<unsigned>(sdfMax, value);
if (value > 127)
++sdfLit;
}
std::ostringstream message;
message << "Text mask debug for '" << text << "': alpha min/max/lit=" << alphaMin << "/" << alphaMax << "/" << alphaLit
<< ", sdf min/max/gt127=" << sdfMin << "/" << sdfMax << "/" << sdfLit << "\n";
OutputDebugStringA(message.str().c_str());
}
catch (...)
{
OutputDebugStringA("Failed to write text mask debug dump.\n");
}
}
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName)
{
GLint location = glGetUniformLocation(program, samplerName.c_str());
if (location >= 0)
return location;
return glGetUniformLocation(program, (samplerName + "_0").c_str());
}
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error) bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error)
{ {
ULONG_PTR gdiplusToken = 0; GdiplusSession gdiplus;
Gdiplus::GdiplusStartupInput startupInput; if (!gdiplus.started())
if (Gdiplus::GdiplusStartup(&gdiplusToken, &startupInput, NULL) != Gdiplus::Ok)
{ {
error = "Could not start GDI+ for text rendering."; error = "Could not start GDI+ for text rendering.";
return false; return false;
@@ -210,7 +312,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
{ {
if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok) if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok)
{ {
Gdiplus::GdiplusShutdown(gdiplusToken);
error = "Could not load packaged font file for text rendering: " + fontPath.string(); error = "Could not load packaged font file for text rendering: " + fontPath.string();
return false; return false;
} }
@@ -218,7 +319,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
const INT familyCount = fontCollection.GetFamilyCount(); const INT familyCount = fontCollection.GetFamilyCount();
if (familyCount <= 0) if (familyCount <= 0)
{ {
Gdiplus::GdiplusShutdown(gdiplusToken);
error = "Packaged font did not contain a usable font family: " + fontPath.string(); error = "Packaged font did not contain a usable font family: " + fontPath.string();
return false; return false;
} }
@@ -227,7 +327,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
INT found = 0; INT found = 0;
if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || 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(); error = "Could not read the packaged font family: " + fontPath.string();
return false; return false;
} }
@@ -236,7 +335,9 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB); Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB);
Gdiplus::Graphics graphics(&bitmap); Gdiplus::Graphics graphics(&bitmap);
graphics.Clear(Gdiplus::Color(0, 0, 0, 0)); graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics.Clear(Gdiplus::Color(255, 0, 0, 0));
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
Gdiplus::Font font(fontFamily, 72.0f, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); Gdiplus::Font font(fontFamily, 72.0f, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
@@ -256,11 +357,17 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
{ {
Gdiplus::Color pixel; Gdiplus::Color pixel;
bitmap.GetPixel(x, y, &pixel); bitmap.GetPixel(x, y, &pixel);
alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = pixel.GetAlpha(); BYTE luminance = pixel.GetRed();
if (pixel.GetGreen() > luminance)
luminance = pixel.GetGreen();
if (pixel.GetBlue() > luminance)
luminance = pixel.GetBlue();
alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(luminance);
} }
} }
sdf = BuildLocalSdf(alpha, kTextTextureWidth, kTextTextureHeight); sdf = BuildLocalSdf(alpha, kTextTextureWidth, kTextTextureHeight);
Gdiplus::GdiplusShutdown(gdiplusToken); sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight);
WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight);
return true; return true;
} }
@@ -1670,7 +1777,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint); glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap; const GLuint shaderTextureBase = state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
glUseProgram(newProgram.get()); glUseProgram(newProgram.get());
const GLint videoInputLocation = glGetUniformLocation(newProgram.get(), "gVideoInput"); const GLint videoInputLocation = glGetUniformLocation(newProgram.get(), "gVideoInput");
if (videoInputLocation >= 0) if (videoInputLocation >= 0)
@@ -1689,14 +1796,14 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
} }
for (std::size_t index = 0; index < textureBindings.size(); ++index) for (std::size_t index = 0; index < textureBindings.size(); ++index)
{ {
const GLint textureSamplerLocation = glGetUniformLocation(newProgram.get(), textureBindings[index].samplerName.c_str()); const GLint textureSamplerLocation = FindSamplerUniformLocation(newProgram.get(), textureBindings[index].samplerName);
if (textureSamplerLocation >= 0) if (textureSamplerLocation >= 0)
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index))); glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
} }
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(textureBindings.size()); const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(textureBindings.size());
for (std::size_t index = 0; index < textBindings.size(); ++index) for (std::size_t index = 0; index < textBindings.size(); ++index)
{ {
const GLint textSamplerLocation = glGetUniformLocation(newProgram.get(), textBindings[index].samplerName.c_str()); const GLint textSamplerLocation = FindSamplerUniformLocation(newProgram.get(), textBindings[index].samplerName);
if (textSamplerLocation >= 0) if (textSamplerLocation >= 0)
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index))); glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
} }
@@ -1704,6 +1811,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
layerProgram.layerId = state.layerId; layerProgram.layerId = state.layerId;
layerProgram.shaderId = state.shaderId; layerProgram.shaderId = state.shaderId;
layerProgram.shaderTextureBase = shaderTextureBase;
layerProgram.program = newProgram.release(); layerProgram.program = newProgram.release();
layerProgram.vertexShader = newVertexShader.release(); layerProgram.vertexShader = newVertexShader.release();
layerProgram.fragmentShader = newFragmentShader.release(); layerProgram.fragmentShader = newFragmentShader.release();
@@ -1972,9 +2080,18 @@ bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state,
if (!RasterizeTextSdf(text, fontPath, sdf, error)) if (!RasterizeTextSdf(text, fontPath, sdf, error))
return false; return false;
GLint previousActiveTexture = 0;
GLint previousUnpackBuffer = 0;
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
glActiveTexture(GL_TEXTURE0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, textBinding.texture); 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()); 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); glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
textBinding.renderedText = text; textBinding.renderedText = text;
textBinding.renderedWidth = kTextTextureWidth; textBinding.renderedWidth = kTextTextureWidth;
textBinding.renderedHeight = kTextTextureHeight; textBinding.renderedHeight = kTextTextureHeight;
@@ -1983,8 +2100,7 @@ bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state,
void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram) void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram)
{ {
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
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(); ++index)
{ {
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index)); glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
@@ -2023,7 +2139,7 @@ void OpenGLComposite::destroyDecodeShaderProgram()
bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, std::string& error) const bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, std::string& error) const
{ {
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
unsigned maxAssetTextures = 0; unsigned requiredUnits = kSourceHistoryTextureUnitBase;
for (const RuntimeRenderState& state : layerStates) for (const RuntimeRenderState& state : layerStates)
{ {
unsigned textTextureCount = 0; unsigned textTextureCount = 0;
@@ -2033,12 +2149,12 @@ bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector<Runtim
++textTextureCount; ++textTextureCount;
} }
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount; const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
if (totalShaderTextures > maxAssetTextures) const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures;
maxAssetTextures = totalShaderTextures; if (layerRequiredUnits > requiredUnits)
requiredUnits = layerRequiredUnits;
} }
GLint maxTextureUnits = 0; GLint maxTextureUnits = 0;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
const unsigned requiredUnits = kSourceHistoryTextureUnitBase + historyCap + historyCap + maxAssetTextures;
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u; const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
if (requiredUnits > availableUnits) if (requiredUnits > availableUnits)
{ {
@@ -2327,7 +2443,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index); glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap; const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index) for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index)
{ {
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index)); glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));

View File

@@ -180,6 +180,7 @@ private:
std::string layerId; std::string layerId;
std::string shaderId; std::string shaderId;
GLuint shaderTextureBase = 0;
GLuint program = 0; GLuint program = 0;
GLuint vertexShader = 0; GLuint vertexShader = 0;
GLuint fragmentShader = 0; GLuint fragmentShader = 0;

View File

@@ -1571,8 +1571,62 @@ void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, co
{ {
for (const ShaderParameterDefinition& definition : shaderPackage.parameters) for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
{ {
if (layerState.parameterValues.find(definition.id) == layerState.parameterValues.end()) auto valueIt = layerState.parameterValues.find(definition.id);
if (valueIt == layerState.parameterValues.end())
{
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition); layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
continue;
}
JsonValue valueJson;
bool shouldNormalize = true;
switch (definition.type)
{
case ShaderParameterType::Float:
if (valueIt->second.numberValues.empty())
shouldNormalize = false;
else
valueJson = JsonValue(valueIt->second.numberValues.front());
break;
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
valueJson = JsonValue::MakeArray();
for (double number : valueIt->second.numberValues)
valueJson.pushBack(JsonValue(number));
break;
case ShaderParameterType::Boolean:
valueJson = JsonValue(valueIt->second.booleanValue);
break;
case ShaderParameterType::Enum:
valueJson = JsonValue(valueIt->second.enumValue);
break;
case ShaderParameterType::Text:
{
const std::string textValue = !valueIt->second.textValue.empty()
? valueIt->second.textValue
: valueIt->second.enumValue;
if (textValue.empty())
{
valueIt->second = DefaultValueForDefinition(definition);
shouldNormalize = false;
}
else
{
valueJson = JsonValue(textValue);
}
break;
}
}
if (!shouldNormalize)
continue;
ShaderParameterValue normalizedValue;
std::string normalizeError;
if (NormalizeAndValidateValue(definition, valueJson, normalizedValue, normalizeError))
valueIt->second = normalizedValue;
else
valueIt->second = DefaultValueForDefinition(definition);
} }
} }

View File

@@ -100,6 +100,8 @@ std::string BuildTextHelpers(const std::vector<ShaderParameterDefinition>& param
source source
<< "float sample" << suffix << "(float2 uv)\n" << "float sample" << suffix << "(float2 uv)\n"
<< "{\n" << "{\n"
<< "\tif (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)\n"
<< "\t\treturn 0.0;\n"
<< "\treturn " << definition.id << "Texture.Sample(uv).r;\n" << "\treturn " << definition.id << "Texture.Sample(uv).r;\n"
<< "}\n\n" << "}\n\n"
<< "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n" << "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n"
@@ -166,13 +168,14 @@ bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage,
return false; return false;
wrapperSource = ReplaceAll(wrapperSource, "{{PARAMETER_UNIFORMS}}", BuildParameterUniforms(shaderPackage.parameters)); wrapperSource = ReplaceAll(wrapperSource, "{{PARAMETER_UNIFORMS}}", BuildParameterUniforms(shaderPackage.parameters));
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", mMaxTemporalHistoryFrames)); const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0;
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", mMaxTemporalHistoryFrames)); wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets)); wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets));
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters)); wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters));
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters)); wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters));
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", mMaxTemporalHistoryFrames)); wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", mMaxTemporalHistoryFrames)); wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string()); wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string());
wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", shaderPackage.entryPoint + "(context)"); wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", shaderPackage.entryPoint + "(context)");
return true; return true;

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -15,11 +15,16 @@ float4 shadeVideo(ShaderContext context)
float2 resolution = max(context.outputResolution, float2(1.0, 1.0)); float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
float aspect = resolution.x / resolution.y; float aspect = resolution.x / resolution.y;
float2 textSize = float2(0.72 * scale, 0.09 * scale * aspect); float2 textSize = float2(0.72 * scale, 0.09 * scale * aspect);
float2 textUv = (context.uv - position) / max(textSize, float2(0.0001, 0.0001)); float2 safeTextSize = max(textSize, float2(0.0001, 0.0001));
float2 textUv = (context.uv - position) / safeTextSize;
bool insideTextRect = textUv.x >= 0.0 && textUv.x <= 1.0 && textUv.y >= 0.0 && textUv.y <= 1.0;
float mask = sampleTitleText(textUv); float mask = insideTextRect ? sampleTitleText(textUv) : 0.0;
float fill = smoothstep(0.48, 0.54, mask); float fill = smoothstep(0.48, 0.54, mask);
float outline = smoothstep(0.48 - outlineWidth, 0.54 - outlineWidth, mask); float outline = smoothstep(0.48 - outlineWidth, 0.54 - outlineWidth, mask);
float textAlpha = max(fill * fillColor.a, outline * outlineColor.a);
if (textAlpha <= 0.0001)
return context.sourceColor;
float4 base = context.sourceColor; float4 base = context.sourceColor;
float4 outlineLayer = float4(outlineColor.rgb * outlineColor.a, outline * outlineColor.a); float4 outlineLayer = float4(outlineColor.rgb * outlineColor.a, outline * outlineColor.a);

View File

@@ -280,6 +280,7 @@ export function ParameterField({ layer, parameter, onParameterChange }) {
<input <input
type="text" type="text"
maxLength={parameter.maxLength ?? 64} maxLength={parameter.maxLength ?? 64}
placeholder={parameter.defaultValue ? `Default: ${parameter.defaultValue}` : ""}
value={draftValue ?? ""} value={draftValue ?? ""}
onFocus={beginInteraction} onFocus={beginInteraction}
onChange={(event) => sendValue(event.target.value)} onChange={(event) => sendValue(event.target.value)}

View File

@@ -5,33 +5,34 @@ function valuesMatch(left, right) {
} }
export function useThrottledParameterValue(parameter, onParameterChange) { export function useThrottledParameterValue(parameter, onParameterChange) {
const [draftValue, setDraftValue] = useState(parameter.value); const currentValue = parameter.value === undefined ? parameter.defaultValue : parameter.value;
const [appliedValue, setAppliedValue] = useState(parameter.value); const [draftValue, setDraftValue] = useState(currentValue);
const [appliedValue, setAppliedValue] = useState(currentValue);
const pendingTimeoutRef = useRef(null); const pendingTimeoutRef = useRef(null);
const latestDraftRef = useRef(parameter.value); const latestDraftRef = useRef(currentValue);
const lastSentAtRef = useRef(0); const lastSentAtRef = useRef(0);
const isInteractingRef = useRef(false); const isInteractingRef = useRef(false);
const isDirtyRef = useRef(false); const isDirtyRef = useRef(false);
useEffect(() => { useEffect(() => {
setDraftValue(parameter.value); setDraftValue(currentValue);
setAppliedValue(parameter.value); setAppliedValue(currentValue);
latestDraftRef.current = parameter.value; latestDraftRef.current = currentValue;
lastSentAtRef.current = 0; lastSentAtRef.current = 0;
isInteractingRef.current = false; isInteractingRef.current = false;
isDirtyRef.current = false; isDirtyRef.current = false;
}, [parameter.id]); }, [parameter.id]);
useEffect(() => { useEffect(() => {
setAppliedValue(parameter.value); setAppliedValue(currentValue);
latestDraftRef.current = draftValue; latestDraftRef.current = draftValue;
if (isDirtyRef.current && valuesMatch(parameter.value, latestDraftRef.current)) { if (isDirtyRef.current && valuesMatch(currentValue, latestDraftRef.current)) {
isDirtyRef.current = false; isDirtyRef.current = false;
} }
if (!isInteractingRef.current && !isDirtyRef.current) { if (!isInteractingRef.current && !isDirtyRef.current) {
setDraftValue(parameter.value); setDraftValue(currentValue);
} }
}, [draftValue, parameter.value]); }, [draftValue, currentValue]);
useEffect(() => { useEffect(() => {
return () => { return () => {