Added 51-gpufont example. (#3641)

This commit is contained in:
Branimir Karadžić
2026-03-22 19:34:05 -07:00
committed by GitHub
parent 3118337be2
commit a7016487e5
23 changed files with 1810 additions and 2 deletions

View File

@@ -710,3 +710,17 @@ Reference(s):
Demonstrate running bgfx in headless mode. Initialize bgfx without window, render into frame buffer,
and output result into an image.
`51-gpufont <https://github.com/bkaradzic/bgfx/tree/master/examples/51-gpufont>`__
----------------------------------------------------------------------------------
GPU font rendering using the Slug algorithm.
Slug reference shaders by Eric Lengyel (MIT License).
https://github.com/EricLengyel/Slug?tab=readme-ov-file#slug
For more information see:
https://sluglibrary.com/
.. figure:: https://github.com/bkaradzic/bgfx/raw/master/examples/51-gpufont/screenshot.png
:alt: example-51-gpufont

View File

@@ -0,0 +1,254 @@
$input v_texcoord0, v_banding, v_glyph
// ===================================================
// Reference pixel shader for the Slug algorithm.
// This code is made available under the MIT License.
// Copyright 2017, by Eric Lengyel.
// ===================================================
#include "../common/common.sh"
// The curve and band textures use a fixed width of 4096 texels.
#define kLogBandTextureWidth 12
SAMPLER2D(s_curveData, 0);
SAMPLER2D(s_bandData, 1);
uniform vec4 u_params;
int calcRootCode(float _y1, float _y2, float _y3)
{
// Calculate the root eligibility code for a sample-relative quadratic Bezier curve.
// Extract the signs of the y coordinates of the three control points.
int i1 = (_y1 < 0.0) ? 1 : 0;
int i2 = (_y2 < 0.0) ? 2 : 0;
int i3 = (_y3 < 0.0) ? 4 : 0;
int shift = i1 + i2 + i3;
// Eligibility is returned in bits 0 and 8.
return (0x2E74 >> shift) & 0x0101;
}
vec2 solveHorizPoly(vec4 _p12, vec2 _p3)
{
// Solve for the values of t where the curve crosses y = 0.
// The quadratic polynomial in t is given by
//
// a t^2 - 2b t + c,
//
// where a = p1.y - 2 p2.y + p3.y, b = p1.y - p2.y, and c = p1.y.
// The discriminant b^2 - ac is clamped to zero, and imaginary
// roots are treated as a double root at the global minimum
// where t = b / a.
vec2 a = _p12.xy - _p12.zw * 2.0 + _p3;
vec2 b = _p12.xy - _p12.zw;
float ra = 1.0 / a.y;
float rb = 0.5 / b.y;
float d = sqrt(max(b.y * b.y - a.y * _p12.y, 0.0) );
float t1 = (b.y - d) * ra;
float t2 = (b.y + d) * ra;
// If the polynomial is nearly linear, then solve -2b t + c = 0.
if (abs(a.y) < 1.0 / 65536.0)
{
t1 = t2 = _p12.y * rb;
}
// Return the x coordinates where C(t) = 0.
return vec2( (a.x * t1 - b.x * 2.0) * t1 + _p12.x, (a.x * t2 - b.x * 2.0) * t2 + _p12.x);
}
vec2 solveVertPoly(vec4 _p12, vec2 _p3)
{
// Solve for the values of t where the curve crosses x = 0.
vec2 a = _p12.xy - _p12.zw * 2.0 + _p3;
vec2 b = _p12.xy - _p12.zw;
float ra = 1.0 / a.x;
float rb = 0.5 / b.x;
float d = sqrt(max(b.x * b.x - a.x * _p12.x, 0.0) );
float t1 = (b.x - d) * ra;
float t2 = (b.x + d) * ra;
if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = _p12.x * rb;
return vec2(
(a.y * t1 - b.y * 2.0) * t1 + _p12.y
, (a.y * t2 - b.y * 2.0) * t2 + _p12.y
);
}
ivec2 calcBandLoc(ivec2 glyphLoc, int offset)
{
// If the offset causes the x coordinate to exceed the texture width, then wrap to the next line.
ivec2 bandLoc = ivec2(glyphLoc.x + offset, glyphLoc.y);
bandLoc.y += bandLoc.x >> kLogBandTextureWidth;
bandLoc.x &= (1 << kLogBandTextureWidth) - 1;
return bandLoc;
}
float calcCoverage(float xcov, float ycov, float xwgt, float ywgt)
{
// Combine coverages from the horizontal and vertical rays using their weights.
// Absolute values ensure that either winding direction convention works.
float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov) ));
// Using nonzero fill rule here.
coverage = clamp(coverage, 0.0, 1.0);
return coverage;
}
float slugRender(vec2 _renderCoord, vec4 _bandTransform, ivec2 _glyphLoc, ivec2 _bandMax)
{
int curveIndex;
// The effective pixel dimensions of the em square are computed
// independently for x and y directions with texcoord derivatives.
vec2 emsPerPixel = fwidth(_renderCoord);
vec2 pixelsPerEm = 1.0 / emsPerPixel;
// Determine what bands the current pixel lies in by applying a scale and offset
// to the render coordinates. The scales are given by _bandTransform.xy, and the
// offsets are given by _bandTransform.zw. Band indexes are clamped to [0, _bandMax.xy].
ivec2 bandIndex = clamp(ivec2(_renderCoord * _bandTransform.xy + _bandTransform.zw), ivec2(0, 0), _bandMax);
float xcov = 0.0;
float xwgt = 0.0;
// Fetch data for the horizontal band from the index texture. The number
// of curves intersecting the band is in the x component, and the offset
// to the list of locations for those curves is in the y component.
vec4 hbandRaw = texelFetch(s_bandData, ivec2(_glyphLoc.x + bandIndex.y, _glyphLoc.y), 0);
int hbandCount = int(hbandRaw.x + 0.5);
int hbandOffset = int(hbandRaw.y + 0.5);
ivec2 hbandLoc = calcBandLoc(_glyphLoc, hbandOffset);
// Loop over all curves in the horizontal band.
for (curveIndex = 0; curveIndex < hbandCount; curveIndex++)
{
// Fetch the location of the current curve from the index texture.
vec4 locRaw = texelFetch(s_bandData, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0);
ivec2 curveLoc = ivec2(int(locRaw.x + 0.5), int(locRaw.y + 0.5) );
// Fetch the three 2D control points for the current curve from the curve texture.
// The first texel contains both p1 and p2 in the (x,y) and (z,w) components, respectively,
// and the the second texel contains p3 in the (x,y) components. Subtracting the render
// coordinates makes the curve relative to the sample position. The quadratic Bezier curve
// C(t) is given by
//
// C(t) = (1 - t)^2 p1 + 2t(1 - t) p2 + t^2 p3
vec4 p12 = texelFetch(s_curveData, curveLoc, 0) - vec4(_renderCoord, _renderCoord);
vec2 p3 = texelFetch(s_curveData, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - _renderCoord;
// If the largest x coordinate among all three control points falls
// left of the current pixel, then there are no more curves in the
// horizontal band that can influence the result, so exit the loop.
// (The curves are sorted in descending order by max x coordinate.)
if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;
int code = calcRootCode(p12.y, p12.w, p3.y);
if (code != 0)
{
vec2 r = solveHorizPoly(p12, p3) * pixelsPerEm.x;
if ( (code & 1) != 0)
{
xcov += clamp(r.x + 0.5, 0.0, 1.0);
xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0) );
}
if (code > 1)
{
xcov -= clamp(r.y + 0.5, 0.0, 1.0);
xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0) );
}
}
}
float ycov = 0.0;
float ywgt = 0.0;
// Fetch data for the vertical band from the index texture. This follows
// the data for all horizontal bands, so we have to add _bandMax.y + 1.
vec4 vbandRaw = texelFetch(s_bandData, ivec2(_glyphLoc.x + _bandMax.y + 1 + bandIndex.x, _glyphLoc.y), 0);
int vbandCount = int(vbandRaw.x + 0.5);
int vbandOffset = int(vbandRaw.y + 0.5);
ivec2 vbandLoc = calcBandLoc(_glyphLoc, vbandOffset);
// Loop over all curves in the vertical band.
for (curveIndex = 0; curveIndex < vbandCount; curveIndex++)
{
vec4 locRaw = texelFetch(s_bandData, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0);
ivec2 curveLoc = ivec2(int(locRaw.x + 0.5), int(locRaw.y + 0.5) );
vec4 p12 = texelFetch(s_curveData, curveLoc, 0) - vec4(_renderCoord, _renderCoord);
vec2 p3 = texelFetch(s_curveData, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - _renderCoord;
if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;
int code = calcRootCode(p12.x, p12.z, p3.x);
if (code != 0)
{
vec2 r = solveVertPoly(p12, p3) * pixelsPerEm.y;
if ( (code & 1) != 0)
{
ycov -= clamp(r.x + 0.5, 0.0, 1.0);
ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0) );
}
if (code > 1)
{
ycov += clamp(r.y + 0.5, 0.0, 1.0);
ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0) );
}
}
}
return calcCoverage(xcov, ycov, xwgt, ywgt);
}
vec4 unpackRGBA8(float _packed)
{
uint rgba = floatBitsToUint(_packed);
return vec4(
(rgba >> 24) & 0xffu
, (rgba >> 16) & 0xffu
, (rgba >> 8) & 0xffu
, (rgba ) & 0xffu
) / 255.0
;
}
void main()
{
ivec2 glyphLoc = ivec2(v_glyph.xy);
ivec2 bandMax = ivec2(v_glyph.zw);
bandMax.y &= 0x00FF;
float coverage = slugRender(v_texcoord0, v_banding, glyphLoc, bandMax);
vec4 fg = unpackRGBA8(u_params.x);
vec4 bg = unpackRGBA8(u_params.y);
gl_FragColor = mix(bg, fg, coverage);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
#
# Copyright 2011-2026 Branimir Karadzic. All rights reserved.
# License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
#
BGFX_DIR=../..
RUNTIME_DIR=$(BGFX_DIR)/examples/runtime
BUILD_DIR=../../.build
include $(BGFX_DIR)/scripts/shader.mk

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

View File

@@ -0,0 +1,9 @@
vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0);
flat vec4 v_banding : TEXCOORD1 = vec4(0.0, 0.0, 0.0, 0.0);
flat vec4 v_glyph : TEXCOORD2 = vec4(0.0, 0.0, 0.0, 0.0);
vec2 v_position : TEXCOORD3 = vec2(0.0, 0.0);
vec4 a_position : POSITION;
vec4 a_texcoord0 : TEXCOORD0;
vec4 a_texcoord1 : TEXCOORD1;
vec2 a_texcoord2 : TEXCOORD2;

View File

@@ -0,0 +1,95 @@
$input a_position, a_texcoord0, a_texcoord1
$output v_texcoord0, v_banding, v_glyph
// ===================================================
// Reference vertex shader for the Slug algorithm.
// This code is made available under the MIT License.
// Copyright 2017, by Eric Lengyel.
// ===================================================
#include "../common/common.sh"
uniform vec4 u_params;
// The per-vertex input data consists of 5 attributes all having 4 floating-point components:
//
// 0 - pos
// 1 - tex
// 2 - jac
// 3 - bnd
// 4 - col
// pos.xy = object-space vertex coordinates.
// pos.zw = object-space normal vector.
// tex.xy = em-space sample coordinates.
// tex.z = location of glyph data in band texture (interpreted as integer):
// | 31 24 | 23 16 | 15 8 | 7 0 |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | y coordinate of glyph data in band texture | x coordinate of glyph data in band texture |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// tex.w = max band indexes and flags (interpreted as integer):
// | 31 24 | 23 16 | 15 8 | 7 0 |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | 0 0 0 | E | 0 0 0 0 | band max y | 0 0 0 0 0 0 0 0 | band max x |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// jac = inverse Jacobian matrix entries (00, 01, 10, 11).
// bnd = (band scale x, band scale y, band offset x, band offset y).
// col = vertex color (red, green, blue, alpha).
void slugUnpack(vec4 _bnd, vec2 _bits, out vec4 _outBanding, out vec4 _outGlyph)
{
uvec2 g = floatBitsToUint(_bits);
_outGlyph = vec4(g.x & 0xffffu, g.x >> 16u, g.y & 0xffffu, g.y >> 16u);
_outBanding = _bnd;
}
vec2 slugDilate(vec2 _pos, vec2 _normal, vec2 _tex, vec4 _jac, vec2 _dim, out vec2 _outPos)
{
vec2 n = normalize(_normal);
vec4 pClip = mul(u_modelViewProj, vec4(_pos, 0.0, 1.0) );
vec4 nClip = mul(u_modelViewProj, vec4(n, 0.0, 0.0) );
float s = pClip.w;
float t = nClip.w;
float u = (s * nClip.x - t * pClip.x) * _dim.x;
float v = (s * nClip.y - t * pClip.y) * _dim.y;
float s2 = s * s;
float st = s * t;
float uv = u * u + v * v;
vec2 d = _normal * (s2 * (st + sqrt(uv) ) / (uv - st * st) );
_outPos = _pos + d;
return vec2(_tex.x + dot(d, _jac.xy), _tex.y + dot(d, _jac.zw) );
}
void main()
{
vec2 p;
vec4 jacobian = vec4(u_params.w, 0.0, 0.0, u_params.w);
// Apply dynamic dilation to vertex position. Returns new em-space sample position.
vec2 pos = a_position.xy;
vec2 normal = a_position.zw;
vec2 uv = a_texcoord0.xy;
vec2 bits = a_texcoord0.zw;
v_texcoord0 = slugDilate(pos, normal, uv, jacobian, u_viewRect.zw, p);
// Apply MVP matrix to dilated vertex position.
gl_Position = mul(u_modelViewProj, vec4(p, 0.0, 1.0) );
// Unpack or pass through remaining vertex data.
slugUnpack(a_texcoord1, bits, v_banding, v_glyph);
}

View File

@@ -471,8 +471,6 @@ void showExampleDialog(entry::AppI* _app, const char* _errorText)
{
if (ImGui::CollapsingHeader(ICON_FA_PUZZLE_PIECE " Resources") )
{
const bgfx::Caps* caps = bgfx::getCaps();
const float itemHeight = ImGui::GetTextLineHeightWithSpacing();
const float maxWidth = 90.0f;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -574,6 +574,7 @@ or _OPTIONS["with-combined-examples"] then
, "47-pixelformats"
, "48-drawindirect"
, "49-hextile"
, "51-gpufont"
)