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

@@ -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));