Font working
This commit is contained in:
@@ -236,7 +236,10 @@ Fonts
|
||||
genlock
|
||||
Logs
|
||||
anamorphic desqueeze
|
||||
solid color layer
|
||||
refactor, cleanup of source files
|
||||
display URL (Maybe clicakable) for control in the windows app (Not on the output)
|
||||
Sound shader as seperate .slang in shader package?
|
||||
runtime date time
|
||||
Add a value control to the color wheels
|
||||

|
||||
@@ -47,6 +47,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <gdiplus.h>
|
||||
#include <wincodec.h>
|
||||
#include <limits>
|
||||
@@ -106,6 +107,31 @@ const char* kDecodeFragmentShaderSource =
|
||||
" fragColor = rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
class GdiplusSession
|
||||
{
|
||||
public:
|
||||
GdiplusSession()
|
||||
{
|
||||
Gdiplus::GdiplusStartupInput startupInput;
|
||||
mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok;
|
||||
}
|
||||
|
||||
~GdiplusSession()
|
||||
{
|
||||
if (mStarted)
|
||||
Gdiplus::GdiplusShutdown(mToken);
|
||||
}
|
||||
|
||||
GdiplusSession(const GdiplusSession&) = delete;
|
||||
GdiplusSession& operator=(const GdiplusSession&) = delete;
|
||||
|
||||
bool started() const { return mStarted; }
|
||||
|
||||
private:
|
||||
ULONG_PTR mToken = 0;
|
||||
bool mStarted = false;
|
||||
};
|
||||
|
||||
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
if (!errorMessage || errorMessageSize <= 0)
|
||||
@@ -191,11 +217,87 @@ std::vector<unsigned char> BuildLocalSdf(const std::vector<unsigned char>& alpha
|
||||
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)
|
||||
{
|
||||
ULONG_PTR gdiplusToken = 0;
|
||||
Gdiplus::GdiplusStartupInput startupInput;
|
||||
if (Gdiplus::GdiplusStartup(&gdiplusToken, &startupInput, NULL) != Gdiplus::Ok)
|
||||
GdiplusSession gdiplus;
|
||||
if (!gdiplus.started())
|
||||
{
|
||||
error = "Could not start GDI+ for text rendering.";
|
||||
return false;
|
||||
@@ -210,7 +312,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
|
||||
{
|
||||
if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok)
|
||||
{
|
||||
Gdiplus::GdiplusShutdown(gdiplusToken);
|
||||
error = "Could not load packaged font file for text rendering: " + fontPath.string();
|
||||
return false;
|
||||
}
|
||||
@@ -218,7 +319,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
|
||||
const INT familyCount = fontCollection.GetFamilyCount();
|
||||
if (familyCount <= 0)
|
||||
{
|
||||
Gdiplus::GdiplusShutdown(gdiplusToken);
|
||||
error = "Packaged font did not contain a usable font family: " + fontPath.string();
|
||||
return false;
|
||||
}
|
||||
@@ -227,7 +327,6 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
|
||||
INT found = 0;
|
||||
if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0)
|
||||
{
|
||||
Gdiplus::GdiplusShutdown(gdiplusToken);
|
||||
error = "Could not read the packaged font family: " + fontPath.string();
|
||||
return false;
|
||||
}
|
||||
@@ -236,7 +335,9 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
|
||||
|
||||
Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB);
|
||||
Gdiplus::Graphics graphics(&bitmap);
|
||||
graphics.Clear(Gdiplus::Color(0, 0, 0, 0));
|
||||
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
||||
graphics.Clear(Gdiplus::Color(255, 0, 0, 0));
|
||||
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
|
||||
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit);
|
||||
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
|
||||
Gdiplus::Font font(fontFamily, 72.0f, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
|
||||
@@ -256,11 +357,17 @@ bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& font
|
||||
{
|
||||
Gdiplus::Color pixel;
|
||||
bitmap.GetPixel(x, y, &pixel);
|
||||
alpha[static_cast<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);
|
||||
Gdiplus::GdiplusShutdown(gdiplusToken);
|
||||
sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight);
|
||||
WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1670,7 +1777,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
||||
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
||||
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||
const GLuint shaderTextureBase = state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
|
||||
glUseProgram(newProgram.get());
|
||||
const GLint videoInputLocation = glGetUniformLocation(newProgram.get(), "gVideoInput");
|
||||
if (videoInputLocation >= 0)
|
||||
@@ -1689,14 +1796,14 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
||||
}
|
||||
for (std::size_t index = 0; index < textureBindings.size(); ++index)
|
||||
{
|
||||
const GLint textureSamplerLocation = glGetUniformLocation(newProgram.get(), textureBindings[index].samplerName.c_str());
|
||||
const GLint textureSamplerLocation = FindSamplerUniformLocation(newProgram.get(), textureBindings[index].samplerName);
|
||||
if (textureSamplerLocation >= 0)
|
||||
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
|
||||
}
|
||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(textureBindings.size());
|
||||
for (std::size_t index = 0; index < textBindings.size(); ++index)
|
||||
{
|
||||
const GLint textSamplerLocation = glGetUniformLocation(newProgram.get(), textBindings[index].samplerName.c_str());
|
||||
const GLint textSamplerLocation = FindSamplerUniformLocation(newProgram.get(), textBindings[index].samplerName);
|
||||
if (textSamplerLocation >= 0)
|
||||
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
|
||||
}
|
||||
@@ -1704,6 +1811,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
||||
|
||||
layerProgram.layerId = state.layerId;
|
||||
layerProgram.shaderId = state.shaderId;
|
||||
layerProgram.shaderTextureBase = shaderTextureBase;
|
||||
layerProgram.program = newProgram.release();
|
||||
layerProgram.vertexShader = newVertexShader.release();
|
||||
layerProgram.fragmentShader = newFragmentShader.release();
|
||||
@@ -1972,9 +2080,18 @@ bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state,
|
||||
if (!RasterizeTextSdf(text, fontPath, sdf, error))
|
||||
return false;
|
||||
|
||||
GLint previousActiveTexture = 0;
|
||||
GLint previousUnpackBuffer = 0;
|
||||
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
|
||||
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
|
||||
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
||||
|
||||
textBinding.renderedText = text;
|
||||
textBinding.renderedWidth = kTextTextureWidth;
|
||||
textBinding.renderedHeight = kTextTextureHeight;
|
||||
@@ -1983,8 +2100,7 @@ bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state,
|
||||
|
||||
void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram)
|
||||
{
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||
const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
|
||||
for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
||||
@@ -2023,7 +2139,7 @@ void OpenGLComposite::destroyDecodeShaderProgram()
|
||||
bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, std::string& error) const
|
||||
{
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
unsigned maxAssetTextures = 0;
|
||||
unsigned requiredUnits = kSourceHistoryTextureUnitBase;
|
||||
for (const RuntimeRenderState& state : layerStates)
|
||||
{
|
||||
unsigned textTextureCount = 0;
|
||||
@@ -2033,12 +2149,12 @@ bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector<Runtim
|
||||
++textTextureCount;
|
||||
}
|
||||
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
|
||||
if (totalShaderTextures > maxAssetTextures)
|
||||
maxAssetTextures = totalShaderTextures;
|
||||
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures;
|
||||
if (layerRequiredUnits > requiredUnits)
|
||||
requiredUnits = layerRequiredUnits;
|
||||
}
|
||||
GLint maxTextureUnits = 0;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
|
||||
const unsigned requiredUnits = kSourceHistoryTextureUnitBase + historyCap + historyCap + maxAssetTextures;
|
||||
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
|
||||
if (requiredUnits > availableUnits)
|
||||
{
|
||||
@@ -2327,7 +2443,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
|
||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||
const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
|
||||
for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
||||
|
||||
@@ -180,6 +180,7 @@ private:
|
||||
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
GLuint shaderTextureBase = 0;
|
||||
GLuint program = 0;
|
||||
GLuint vertexShader = 0;
|
||||
GLuint fragmentShader = 0;
|
||||
|
||||
@@ -1571,8 +1571,62 @@ void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, co
|
||||
{
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
{
|
||||
if (layerState.parameterValues.find(definition.id) == layerState.parameterValues.end())
|
||||
auto valueIt = layerState.parameterValues.find(definition.id);
|
||||
if (valueIt == layerState.parameterValues.end())
|
||||
{
|
||||
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonValue valueJson;
|
||||
bool shouldNormalize = true;
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
if (valueIt->second.numberValues.empty())
|
||||
shouldNormalize = false;
|
||||
else
|
||||
valueJson = JsonValue(valueIt->second.numberValues.front());
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
valueJson = JsonValue::MakeArray();
|
||||
for (double number : valueIt->second.numberValues)
|
||||
valueJson.pushBack(JsonValue(number));
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
valueJson = JsonValue(valueIt->second.booleanValue);
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
valueJson = JsonValue(valueIt->second.enumValue);
|
||||
break;
|
||||
case ShaderParameterType::Text:
|
||||
{
|
||||
const std::string textValue = !valueIt->second.textValue.empty()
|
||||
? valueIt->second.textValue
|
||||
: valueIt->second.enumValue;
|
||||
if (textValue.empty())
|
||||
{
|
||||
valueIt->second = DefaultValueForDefinition(definition);
|
||||
shouldNormalize = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueJson = JsonValue(textValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldNormalize)
|
||||
continue;
|
||||
|
||||
ShaderParameterValue normalizedValue;
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateValue(definition, valueJson, normalizedValue, normalizeError))
|
||||
valueIt->second = normalizedValue;
|
||||
else
|
||||
valueIt->second = DefaultValueForDefinition(definition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,8 @@ std::string BuildTextHelpers(const std::vector<ShaderParameterDefinition>& param
|
||||
source
|
||||
<< "float sample" << suffix << "(float2 uv)\n"
|
||||
<< "{\n"
|
||||
<< "\tif (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)\n"
|
||||
<< "\t\treturn 0.0;\n"
|
||||
<< "\treturn " << definition.id << "Texture.Sample(uv).r;\n"
|
||||
<< "}\n\n"
|
||||
<< "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n"
|
||||
@@ -166,13 +168,14 @@ bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage,
|
||||
return false;
|
||||
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{PARAMETER_UNIFORMS}}", BuildParameterUniforms(shaderPackage.parameters));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", mMaxTemporalHistoryFrames));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", mMaxTemporalHistoryFrames));
|
||||
const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0;
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", mMaxTemporalHistoryFrames));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", mMaxTemporalHistoryFrames));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string());
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", shaderPackage.entryPoint + "(context)");
|
||||
return true;
|
||||
|
||||
@@ -15,11 +15,16 @@ float4 shadeVideo(ShaderContext context)
|
||||
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
|
||||
float aspect = resolution.x / resolution.y;
|
||||
float2 textSize = float2(0.72 * scale, 0.09 * scale * aspect);
|
||||
float2 textUv = (context.uv - position) / max(textSize, float2(0.0001, 0.0001));
|
||||
float2 safeTextSize = max(textSize, float2(0.0001, 0.0001));
|
||||
float2 textUv = (context.uv - position) / safeTextSize;
|
||||
bool insideTextRect = textUv.x >= 0.0 && textUv.x <= 1.0 && textUv.y >= 0.0 && textUv.y <= 1.0;
|
||||
|
||||
float mask = sampleTitleText(textUv);
|
||||
float mask = insideTextRect ? sampleTitleText(textUv) : 0.0;
|
||||
float fill = smoothstep(0.48, 0.54, mask);
|
||||
float outline = smoothstep(0.48 - outlineWidth, 0.54 - outlineWidth, mask);
|
||||
float textAlpha = max(fill * fillColor.a, outline * outlineColor.a);
|
||||
if (textAlpha <= 0.0001)
|
||||
return context.sourceColor;
|
||||
|
||||
float4 base = context.sourceColor;
|
||||
float4 outlineLayer = float4(outlineColor.rgb * outlineColor.a, outline * outlineColor.a);
|
||||
|
||||
@@ -280,6 +280,7 @@ export function ParameterField({ layer, parameter, onParameterChange }) {
|
||||
<input
|
||||
type="text"
|
||||
maxLength={parameter.maxLength ?? 64}
|
||||
placeholder={parameter.defaultValue ? `Default: ${parameter.defaultValue}` : ""}
|
||||
value={draftValue ?? ""}
|
||||
onFocus={beginInteraction}
|
||||
onChange={(event) => sendValue(event.target.value)}
|
||||
|
||||
@@ -5,33 +5,34 @@ function valuesMatch(left, right) {
|
||||
}
|
||||
|
||||
export function useThrottledParameterValue(parameter, onParameterChange) {
|
||||
const [draftValue, setDraftValue] = useState(parameter.value);
|
||||
const [appliedValue, setAppliedValue] = useState(parameter.value);
|
||||
const currentValue = parameter.value === undefined ? parameter.defaultValue : parameter.value;
|
||||
const [draftValue, setDraftValue] = useState(currentValue);
|
||||
const [appliedValue, setAppliedValue] = useState(currentValue);
|
||||
const pendingTimeoutRef = useRef(null);
|
||||
const latestDraftRef = useRef(parameter.value);
|
||||
const latestDraftRef = useRef(currentValue);
|
||||
const lastSentAtRef = useRef(0);
|
||||
const isInteractingRef = useRef(false);
|
||||
const isDirtyRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setDraftValue(parameter.value);
|
||||
setAppliedValue(parameter.value);
|
||||
latestDraftRef.current = parameter.value;
|
||||
setDraftValue(currentValue);
|
||||
setAppliedValue(currentValue);
|
||||
latestDraftRef.current = currentValue;
|
||||
lastSentAtRef.current = 0;
|
||||
isInteractingRef.current = false;
|
||||
isDirtyRef.current = false;
|
||||
}, [parameter.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setAppliedValue(parameter.value);
|
||||
setAppliedValue(currentValue);
|
||||
latestDraftRef.current = draftValue;
|
||||
if (isDirtyRef.current && valuesMatch(parameter.value, latestDraftRef.current)) {
|
||||
if (isDirtyRef.current && valuesMatch(currentValue, latestDraftRef.current)) {
|
||||
isDirtyRef.current = false;
|
||||
}
|
||||
if (!isInteractingRef.current && !isDirtyRef.current) {
|
||||
setDraftValue(parameter.value);
|
||||
setDraftValue(currentValue);
|
||||
}
|
||||
}, [draftValue, parameter.value]);
|
||||
}, [draftValue, currentValue]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user